From f9845edce2e52305f9dcc900cc60b058260a092c Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sun, 14 Apr 2024 21:47:39 +0200 Subject: [PATCH 01/11] Fix bug with Hmm it is not here error appears briefly when enabling workflows --- .../FeatureEnabledAccessOrNotFoundWrapper.tsx | 15 +++++++++-- src/pages/workspace/WorkspaceInitialPage.tsx | 26 ++++++++++++++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index 3bcdc1fe3303..701e57d8ea59 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -1,9 +1,10 @@ /* eslint-disable rulesdir/no-negated-variables */ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import useNetwork from '@hooks/useNetwork'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; @@ -34,8 +35,10 @@ type FeatureEnabledAccessOrNotFoundComponentProps = FeatureEnabledAccessOrNotFou function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNotFoundComponentProps) { const isPolicyIDInRoute = !!props.policyID?.length; + const [isPolicyFeatureEnabled, setIsPolicyFeatureEnabled] = useState(PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)); const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id); - const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); + const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !isPolicyFeatureEnabled; + const {isOffline} = useNetwork(); useEffect(() => { if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) { @@ -47,6 +50,14 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPolicyIDInRoute, props.policyID]); + useEffect(() => { + if (props.policy?.pendingFields?.[props.featureName] === 'update' && !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName) && !isOffline) { + return; + } + setIsPolicyFeatureEnabled(PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.policy?.pendingFields?.[props.featureName], PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)]); + if (shouldShowFullScreenLoadingIndicator) { return ; } diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index a6a131f5372c..e332a18ba01b 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -14,6 +14,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useSingleExecution from '@hooks/useSingleExecution'; @@ -33,6 +34,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import type {PolicyFeatureName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; @@ -71,6 +73,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const activeRoute = useNavigationState(getTopmostWorkspacesCentralPaneName); const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); + const {isOffline} = useNetwork(); const policyID = policy?.id ?? ''; const policyName = policy?.name ?? ''; @@ -152,7 +155,18 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = []; - if (policy?.areDistanceRatesEnabled) { + const getFeatureState = useCallback( + (policyFeatureName: PolicyFeatureName) => { + const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, policyFeatureName); + if (policy?.pendingFields?.[policyFeatureName] === 'update' && !isFeatureEnabled && !isOffline) { + return true; + } + return isFeatureEnabled; + }, + [policy, isOffline], + ); + + if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED)) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.distanceRates', icon: Expensicons.Car, @@ -161,7 +175,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (policy?.areWorkflowsEnabled) { + if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED)) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', icon: Expensicons.Workflows, @@ -171,7 +185,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (policy?.areCategoriesEnabled) { + if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED)) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.categories', icon: Expensicons.Folder, @@ -181,7 +195,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (policy?.areTagsEnabled) { + if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED)) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.tags', icon: Expensicons.Tag, @@ -190,7 +204,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (policy?.tax?.trackingEnabled) { + if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED)) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.taxes', icon: Expensicons.Tax, @@ -200,7 +214,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (policy?.areConnectionsEnabled && canUseAccountingIntegrations) { + if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED) && canUseAccountingIntegrations) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.accounting', icon: Expensicons.Sync, From 23445670f1acab99587bb2a29b3f666b747cf453 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sun, 14 Apr 2024 23:30:21 +0200 Subject: [PATCH 02/11] Refactor code --- .../FeatureEnabledAccessOrNotFoundWrapper.tsx | 2 +- src/pages/workspace/WorkspaceInitialPage.tsx | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index 701e57d8ea59..c61a1bbc2c46 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -51,7 +51,7 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo }, [isPolicyIDInRoute, props.policyID]); useEffect(() => { - if (props.policy?.pendingFields?.[props.featureName] === 'update' && !PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName) && !isOffline) { + if (props.policy?.pendingFields?.[props.featureName] === 'update' && !isOffline) { return; } setIsPolicyFeatureEnabled(PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)); diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index e332a18ba01b..fed13ead8b97 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -111,6 +111,8 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const isPaidGroupPolicy = PolicyUtils.isPaidGroupPolicy(policy); const isFreeGroupPolicy = PolicyUtils.isFreeGroupPolicy(policy); + const [featureStates, setFeatureStates] = useState({} as Record); + const protectedFreePolicyMenuItems: WorkspaceMenuItem[] = [ { translationKey: 'workspace.common.card', @@ -155,18 +157,21 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = []; - const getFeatureState = useCallback( - (policyFeatureName: PolicyFeatureName) => { - const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, policyFeatureName); - if (policy?.pendingFields?.[policyFeatureName] === 'update' && !isFeatureEnabled && !isOffline) { - return true; + useEffect(() => { + const newFeatureStates = {} as Record; + const keys = Object.keys(policy?.pendingFields ?? {}) as PolicyFeatureName[]; + keys.forEach((key) => { + const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, key); + if (policy?.pendingFields?.[key] === 'update' && !isOffline) { + return; } - return isFeatureEnabled; - }, - [policy, isOffline], - ); + newFeatureStates[key] = isFeatureEnabled; + }); + + setFeatureStates(newFeatureStates); + }, [policy, isOffline]); - if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED)) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED] ?? policy?.areDistanceRatesEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.distanceRates', icon: Expensicons.Car, @@ -175,7 +180,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED)) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED] ?? policy?.areWorkflowsEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', icon: Expensicons.Workflows, @@ -185,7 +190,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED)) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED] ?? policy?.areCategoriesEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.categories', icon: Expensicons.Folder, @@ -195,7 +200,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED)) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED] ?? policy?.areTagsEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.tags', icon: Expensicons.Tag, @@ -204,7 +209,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED)) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED] ?? policy?.tax?.trackingEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.taxes', icon: Expensicons.Tax, @@ -214,7 +219,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (getFeatureState(CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED) && canUseAccountingIntegrations) { + if ((featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED] ?? policy?.areConnectionsEnabled) && canUseAccountingIntegrations) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.accounting', icon: Expensicons.Sync, From f8c53afa1c2d6df6be231decca8fa5db4890262a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 14:27:48 +0200 Subject: [PATCH 03/11] Fix comments --- .../FeatureEnabledAccessOrNotFoundWrapper.tsx | 10 ++++++---- src/pages/workspace/WorkspaceInitialPage.tsx | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index c61a1bbc2c46..c997ccc83a5a 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -8,6 +8,7 @@ import useNetwork from '@hooks/useNetwork'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -50,13 +51,14 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPolicyIDInRoute, props.policyID]); + const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); + useEffect(() => { - if (props.policy?.pendingFields?.[props.featureName] === 'update' && !isOffline) { + if (props.policy?.pendingFields?.[props.featureName] === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE && !isFeatureEnabled && !isOffline) { return; } - setIsPolicyFeatureEnabled(PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.policy?.pendingFields?.[props.featureName], PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)]); + setIsPolicyFeatureEnabled(isFeatureEnabled); + }, [props.policy, props.featureName, isFeatureEnabled, isOffline]); if (shouldShowFullScreenLoadingIndicator) { return ; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index fed13ead8b97..b12a931edc3c 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -162,7 +162,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const keys = Object.keys(policy?.pendingFields ?? {}) as PolicyFeatureName[]; keys.forEach((key) => { const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, key); - if (policy?.pendingFields?.[key] === 'update' && !isOffline) { + if (policy?.pendingFields?.[key] === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE && !isFeatureEnabled && !isOffline) { return; } newFeatureStates[key] = isFeatureEnabled; @@ -180,7 +180,9 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED] ?? policy?.areWorkflowsEnabled) { + console.log(featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED], policy?.areWorkflowsEnabled); + + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED] || policy?.areWorkflowsEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', icon: Expensicons.Workflows, From 6f5b56de89473725dce077f2cb7575c141c0fa24 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 14:59:34 +0200 Subject: [PATCH 04/11] Remove console.log --- src/pages/workspace/WorkspaceInitialPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index b12a931edc3c..06197a0712e3 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -180,8 +180,6 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - console.log(featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED], policy?.areWorkflowsEnabled); - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED] || policy?.areWorkflowsEnabled) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', From c7e87a64500b51d5e04329541b922ad65eaf0f6a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 20:10:54 +0200 Subject: [PATCH 05/11] Refactor changes --- .../FeatureEnabledAccessOrNotFoundWrapper.tsx | 22 +++++--- src/pages/workspace/WorkspaceInitialPage.tsx | 56 +++++++++++++------ 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index c997ccc83a5a..8aca7079775e 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -5,10 +5,10 @@ import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useNetwork from '@hooks/useNetwork'; +import usePrevious from '@hooks/usePrevious'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as Policy from '@userActions/Policy'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -36,11 +36,15 @@ type FeatureEnabledAccessOrNotFoundComponentProps = FeatureEnabledAccessOrNotFou function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNotFoundComponentProps) { const isPolicyIDInRoute = !!props.policyID?.length; - const [isPolicyFeatureEnabled, setIsPolicyFeatureEnabled] = useState(PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName)); + const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); + const [isPolicyFeatureEnabled, setIsPolicyFeatureEnabled] = useState(isFeatureEnabled); const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id); const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !isPolicyFeatureEnabled; const {isOffline} = useNetwork(); + const pendingField = props.policy?.pendingFields?.[props.featureName]; + const prevPendingField = usePrevious(pendingField); + useEffect(() => { if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) { // If the workspace is not required or is already loaded, we don't need to call the API @@ -51,14 +55,14 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPolicyIDInRoute, props.policyID]); - const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(props.policy, props.featureName); - useEffect(() => { - if (props.policy?.pendingFields?.[props.featureName] === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE && !isFeatureEnabled && !isOffline) { - return; - } - setIsPolicyFeatureEnabled(isFeatureEnabled); - }, [props.policy, props.featureName, isFeatureEnabled, isOffline]); + setIsPolicyFeatureEnabled((isCurrencyPolicyFeatureEnabled) => { + if (prevPendingField !== pendingField || isOffline || !pendingField) { + return isFeatureEnabled; + } + return isCurrencyPolicyFeatureEnabled; + }); + }, [pendingField, isFeatureEnabled, isOffline, prevPendingField]); if (shouldShowFullScreenLoadingIndicator) { return ; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 06197a0712e3..930d3244d3e2 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -75,6 +75,19 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const {canUseAccountingIntegrations} = usePermissions(); const {isOffline} = useNetwork(); + const prevPendingFields = usePrevious(policy?.pendingFields); + const policyFeatureStates = useMemo( + () => ({ + [CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED]: policy?.areDistanceRatesEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED]: policy?.areWorkflowsEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED]: policy?.areCategoriesEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED]: policy?.areTagsEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]: policy?.tax?.trackingEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED]: policy?.areConnectionsEnabled, + }), + [policy], + ) as Record; + const policyID = policy?.id ?? ''; const policyName = policy?.name ?? ''; @@ -111,7 +124,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const isPaidGroupPolicy = PolicyUtils.isPaidGroupPolicy(policy); const isFreeGroupPolicy = PolicyUtils.isFreeGroupPolicy(policy); - const [featureStates, setFeatureStates] = useState({} as Record); + const [featureStates, setFeatureStates] = useState(policyFeatureStates); const protectedFreePolicyMenuItems: WorkspaceMenuItem[] = [ { @@ -158,20 +171,27 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = []; useEffect(() => { - const newFeatureStates = {} as Record; - const keys = Object.keys(policy?.pendingFields ?? {}) as PolicyFeatureName[]; - keys.forEach((key) => { - const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, key); - if (policy?.pendingFields?.[key] === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE && !isFeatureEnabled && !isOffline) { - return; - } - newFeatureStates[key] = isFeatureEnabled; + setFeatureStates((currentFeatureStates) => { + const newFeatureStates = {} as Record; + const keys = Object.keys(policy?.pendingFields ?? {}) as PolicyFeatureName[]; + keys.forEach((key) => { + const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, key); + if ((prevPendingFields?.[key] !== policy?.pendingFields?.[key]) || isOffline || !policy?.pendingFields?.[key]) { + newFeatureStates[key] = isFeatureEnabled; + + return; + } + + newFeatureStates[key] = currentFeatureStates[key]; + }); + return { + ...policyFeatureStates, + ...newFeatureStates, + }; }); + }, [policy, isOffline, policyFeatureStates, prevPendingFields]); - setFeatureStates(newFeatureStates); - }, [policy, isOffline]); - - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED] ?? policy?.areDistanceRatesEnabled) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.distanceRates', icon: Expensicons.Car, @@ -180,7 +200,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED] || policy?.areWorkflowsEnabled) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', icon: Expensicons.Workflows, @@ -190,7 +210,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED] ?? policy?.areCategoriesEnabled) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.categories', icon: Expensicons.Folder, @@ -200,7 +220,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED] ?? policy?.areTagsEnabled) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.tags', icon: Expensicons.Tag, @@ -209,7 +229,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED] ?? policy?.tax?.trackingEnabled) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.taxes', icon: Expensicons.Tax, @@ -219,7 +239,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r }); } - if ((featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED] ?? policy?.areConnectionsEnabled) && canUseAccountingIntegrations) { + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED] && canUseAccountingIntegrations) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.accounting', icon: Expensicons.Sync, From fc5458426a09bbc3e989f91d2c83b98177e0a9df Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 21:06:13 +0200 Subject: [PATCH 06/11] Fix eslint issue --- src/pages/workspace/WorkspaceInitialPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 930d3244d3e2..da23c303fa75 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -176,7 +176,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const keys = Object.keys(policy?.pendingFields ?? {}) as PolicyFeatureName[]; keys.forEach((key) => { const isFeatureEnabled = PolicyUtils.isPolicyFeatureEnabled(policy, key); - if ((prevPendingFields?.[key] !== policy?.pendingFields?.[key]) || isOffline || !policy?.pendingFields?.[key]) { + if (prevPendingFields?.[key] !== policy?.pendingFields?.[key] || isOffline || !policy?.pendingFields?.[key]) { newFeatureStates[key] = isFeatureEnabled; return; From 9568c9f79e2b5f10a761471845afae104581f75c Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 21:16:21 +0200 Subject: [PATCH 07/11] Fix rare bug with display not found --- src/libs/actions/Policy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 74965bcaca13..ad73e1daa711 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3919,7 +3919,9 @@ function navigateWhenEnableFeature(policyID: string, featureRoute: Route) { new Promise((resolve) => { resolve(); }).then(() => { - Navigation.navigate(featureRoute); + requestAnimationFrame(() => { + Navigation.navigate(featureRoute); + }); }); } From 7484834735ff9e0958f538731aa516bc6041f975 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 15 Apr 2024 21:26:39 +0200 Subject: [PATCH 08/11] Rename const --- src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index 8aca7079775e..b7e12a558f38 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -56,11 +56,11 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo }, [isPolicyIDInRoute, props.policyID]); useEffect(() => { - setIsPolicyFeatureEnabled((isCurrencyPolicyFeatureEnabled) => { + setIsPolicyFeatureEnabled((isCurrentPolicyFeatureEnabled) => { if (prevPendingField !== pendingField || isOffline || !pendingField) { return isFeatureEnabled; } - return isCurrencyPolicyFeatureEnabled; + return isCurrentPolicyFeatureEnabled; }); }, [pendingField, isFeatureEnabled, isOffline, prevPendingField]); From 268b8704f41f3d41f1fc7dc7664f5b23b1285217 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 16 Apr 2024 09:30:30 +0200 Subject: [PATCH 09/11] Fix bug with not found after reload --- src/libs/actions/Policy.ts | 4 ++-- .../workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index ad73e1daa711..9883d2930a05 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3919,9 +3919,9 @@ function navigateWhenEnableFeature(policyID: string, featureRoute: Route) { new Promise((resolve) => { resolve(); }).then(() => { - requestAnimationFrame(() => { + setTimeout(() => { Navigation.navigate(featureRoute); - }); + }, 300); }); } diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index b7e12a558f38..775d8a08c09b 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -1,4 +1,5 @@ /* eslint-disable rulesdir/no-negated-variables */ +import {useIsFocused} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -42,6 +43,8 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !isPolicyFeatureEnabled; const {isOffline} = useNetwork(); + const isFocused = useIsFocused(); + const prevIsFocused = usePrevious(isFocused); const pendingField = props.policy?.pendingFields?.[props.featureName]; const prevPendingField = usePrevious(pendingField); @@ -57,12 +60,12 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo useEffect(() => { setIsPolicyFeatureEnabled((isCurrentPolicyFeatureEnabled) => { - if (prevPendingField !== pendingField || isOffline || !pendingField) { + if (prevPendingField !== pendingField || isOffline || !pendingField || !prevIsFocused) { return isFeatureEnabled; } return isCurrentPolicyFeatureEnabled; }); - }, [pendingField, isFeatureEnabled, isOffline, prevPendingField]); + }, [pendingField, isFeatureEnabled, isOffline, prevPendingField, prevIsFocused]); if (shouldShowFullScreenLoadingIndicator) { return ; From 92a32a765bcefbee11256c36425376230fb6d934 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 16 Apr 2024 22:22:45 +0200 Subject: [PATCH 10/11] Remove unnecessary space and check GPG key --- src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx index 775d8a08c09b..1306c94c9db9 100644 --- a/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/FeatureEnabledAccessOrNotFoundWrapper.tsx @@ -42,7 +42,6 @@ function FeatureEnabledAccessOrNotFoundComponent(props: FeatureEnabledAccessOrNo const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id); const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !isPolicyFeatureEnabled; const {isOffline} = useNetwork(); - const isFocused = useIsFocused(); const prevIsFocused = usePrevious(isFocused); const pendingField = props.policy?.pendingFields?.[props.featureName]; From 601f0c71709b738d22e031bdf35264ea6bbacc92 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sun, 21 Apr 2024 00:47:35 +0200 Subject: [PATCH 11/11] Refactor changes and fix bug with not found after reload page and update branch --- .eslintrc.js | 1 - .github/scripts/detectRedirectCycle.ts | 64 + .github/scripts/verifyRedirect.sh | 26 + .github/workflows/deployExpensifyHelp.yml | 3 + .github/workflows/e2ePerformanceTests.yml | 2 +- .github/workflows/platformDeploy.yml | 13 +- .github/workflows/typecheck.yml | 2 +- .well-known/apple-app-site-association | 10 +- README.md | 10 + android/app/build.gradle | 4 +- .../Deposit-Accounts-USD.md | 77 + .../Business-Bank-Accounts-USD.md | 161 -- .../deposit-accounts/Deposit-Accounts-AUD.md | 22 - .../integrations/HR-integrations/Zenefits.md | 2 +- .../Change-member-workspace-roles.md | 1 + .../Enable-and-set-up-expense-violations.md | 111 ++ docs/redirects.csv | 5 +- docs/sitemap.xml | 2 +- ios/NewExpensify.xcodeproj/project.pbxproj | 8 - ios/NewExpensify/Info.plist | 4 +- ios/NewExpensifyTests/Info.plist | 4 +- ios/NotificationServiceExtension/Info.plist | 4 +- ios/Podfile.lock | 55 +- package-lock.json | 87 +- package.json | 16 +- ...t-native-community+geolocation+3.1.0.patch | 38 - patches/@ua+react-native-airship+15.3.1.patch | 24 - ...ive+0.73.4+014+fix-inverted-flatlist.patch | 23 + ...e-screens+3.30.1+001+fix-screen-type.patch | 12 + src/CONFIG.ts | 3 +- src/CONST.ts | 184 +-- src/ONYXKEYS.ts | 25 +- src/ROUTES.ts | 132 +- src/SCREENS.ts | 26 +- .../AttachmentPicker/index.native.tsx | 24 +- src/components/Avatar.tsx | 2 +- src/components/AvatarWithDisplayName.tsx | 3 +- src/components/ConfirmedRoute.tsx | 2 +- .../index.native.tsx | 68 + .../ConnectToQuickbooksOnlineButton/index.tsx | 25 + .../ConnectToQuickbooksOnlineButton/types.ts | 6 + .../CalendarPicker/YearPickerModal.tsx | 2 +- src/components/DistanceEReceipt.tsx | 2 +- .../DistanceRequest/DistanceRequestFooter.tsx | 2 +- .../DistanceRequestRenderItem.tsx | 2 +- .../BaseHTMLEngineProvider.tsx | 3 + .../HTMLRenderers/VideoRenderer.tsx | 1 + src/components/HoldBanner.tsx | 2 +- src/components/Indicator.tsx | 13 +- src/components/MenuItem.tsx | 10 +- src/components/MoneyReportHeader.tsx | 4 +- .../MoneyRequestConfirmationList.tsx | 953 +++++++----- src/components/MoneyRequestHeader.tsx | 14 +- src/components/MoneyRequestSkeletonView.tsx | 4 +- ...raryForRefactorRequestConfirmationList.tsx | 1053 ------------- .../OptionsList/BaseOptionsList.tsx | 2 +- src/components/OptionsList/types.ts | 2 +- src/components/ParentNavigationSubtitle.tsx | 7 +- src/components/PopoverMenu.tsx | 2 +- .../ActionableItemButtons.tsx | 8 +- .../ReportActionItem/MoneyRequestAction.tsx | 6 +- .../MoneyRequestPreviewContent.tsx | 2 +- .../MoneyRequestPreview/types.ts | 4 +- .../ReportActionItem/MoneyRequestView.tsx | 100 +- .../ReportActionItemImages.tsx | 2 +- .../ReportActionItem/ReportPreview.tsx | 12 +- src/components/SelectionList/BaseListItem.tsx | 14 +- .../SelectionList/BaseSelectionList.tsx | 23 +- .../SelectionList/InviteMemberListItem.tsx | 10 +- .../SelectionList/RadioListItem.tsx | 10 +- .../SelectionList/TableListItem.tsx | 9 +- src/components/SelectionList/UserListItem.tsx | 10 +- src/components/SelectionList/types.ts | 56 +- src/components/SettlementButton.tsx | 4 +- src/components/Switch.tsx | 14 +- src/components/TagPicker/index.tsx | 2 +- src/components/TaskHeaderActionButton.tsx | 17 +- src/components/TestToolMenu.tsx | 11 + .../VideoPlayer/BaseVideoPlayer.tsx | 55 +- .../VideoPlayerContexts/PlaybackContext.tsx | 21 +- src/components/VideoPlayerContexts/types.ts | 2 + src/components/VideoPlayerPreview/index.tsx | 11 +- src/components/transactionPropTypes.js | 2 +- src/hooks/useActiveRoute.ts | 3 +- src/hooks/useDisableModalDismissOnEscape.ts | 9 + src/hooks/useMarkdownStyle.ts | 4 + src/hooks/useStyledSafeAreaInsets.ts | 37 + src/hooks/useViewportOffsetTop/index.ts | 4 +- src/languages/en.ts | 160 +- src/languages/es.ts | 165 ++- src/languages/types.ts | 7 + .../CategorizeTrackedExpenseParams.ts | 25 + .../ConnectPolicyToQuickbooksOnlineParams.ts | 5 + .../ConvertTrackedExpenseToRequestParams.ts | 21 + ...missTrackExpenseActionableWhisperParams.ts | 5 + .../OpenPolicyAccountingPageParams.ts | 5 + .../RemovePolicyConnectionParams.ts | 8 + .../parameters/ShareTrackedExpenseParams.ts | 25 + src/libs/API/parameters/index.ts | 7 + src/libs/API/types.ts | 15 + src/libs/ApiUtils.ts | 6 +- src/libs/CardUtils.ts | 2 +- src/libs/DateUtils.ts | 9 + src/libs/DistanceRequestUtils.ts | 199 ++- src/libs/E2E/reactNativeLaunchingTest.ts | 2 +- ...t.e2e.ts => openChatFinderPageTest.e2e.ts} | 12 +- src/libs/IOUUtils.ts | 32 +- src/libs/ModifiedExpenseMessage.ts | 6 +- src/libs/MoneyRequestUtils.ts | 10 +- .../Navigation/AppNavigator/AuthScreens.tsx | 2 +- .../WorkspaceSettingsModalStackNavigator.tsx | 6 +- .../ModalStackNavigators/index.tsx | 23 +- .../Navigators/ActiveRouteContext.ts | 3 +- .../Navigators/BottomTabNavigator.tsx | 7 +- .../BaseCentralPaneNavigator.tsx | 7 + .../Navigators/LeftModalNavigator.tsx | 4 +- .../AppNavigator/ReportScreenIDSetter.ts | 21 +- .../BottomTabBar.tsx | 8 +- .../createCustomBottomTabNavigator/TopBar.tsx | 2 +- .../index.native.tsx | 44 - .../createCustomStackNavigator/index.tsx | 43 +- .../AppNavigator/getPartialStateDiff.ts | 13 +- src/libs/Navigation/Navigation.ts | 7 - src/libs/Navigation/dismissModalWithReport.ts | 4 +- src/libs/Navigation/dismissRHP.ts | 25 - .../Navigation/getTopmostBottomTabRoute.ts | 5 +- src/libs/Navigation/linkTo.ts | 11 +- .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 1 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 14 +- .../TAB_TO_CENTRAL_PANE_MAPPING.ts | 2 + src/libs/Navigation/linkingConfig/config.ts | 23 +- .../linkingConfig/getAdaptedStateFromPath.ts | 4 +- src/libs/Navigation/types.ts | 171 ++- src/libs/NextStepUtils.ts | 2 +- .../PushNotification/index.native.ts | 22 +- ...bscribeToReportCommentPushNotifications.ts | 64 +- .../Notification/PushNotification/types.ts | 4 +- src/libs/ObjectUtils.ts | 13 + src/libs/OptionsListUtils.ts | 44 +- src/libs/PolicyEmployeeListUtils.ts | 25 + src/libs/PolicyMembersUtils.ts | 25 - src/libs/PolicyUtils.ts | 85 +- src/libs/ReportActionsUtils.ts | 148 +- src/libs/ReportUtils.ts | 862 ++++++----- src/libs/SidebarUtils.ts | 29 +- src/libs/TaskUtils.ts | 8 +- src/libs/TransactionUtils.ts | 36 +- src/libs/WorkspacesSettingsUtils.ts | 61 +- src/libs/actions/IOU.ts | 1303 +++++++++++------ src/libs/actions/Modal.ts | 9 +- src/libs/actions/OnyxUpdateManager.ts | 157 +- src/libs/actions/OnyxUpdates.ts | 22 +- src/libs/actions/PaymentMethods.ts | 3 +- src/libs/actions/Policy.ts | 336 ++--- src/libs/actions/PolicyConnections.ts | 38 + .../resetFreePlanBankAccount.ts | 25 +- src/libs/actions/Report.ts | 114 +- src/libs/actions/ReportActions.ts | 14 +- src/libs/actions/Task.ts | 31 +- src/libs/actions/TeachersUnite.ts | 24 +- src/libs/actions/Transaction.ts | 6 +- src/libs/actions/User.ts | 10 +- src/libs/actions/applyOnyxUpdatesReliably.ts | 26 + .../actions/connections/QuickBooksOnline.ts | 13 + .../getQuickBooksOnlineSetupLink.ts | 13 - src/libs/actions/connections/index.ts | 127 ++ src/libs/fileDownload/FileUtils.ts | 15 + src/libs/getSectionsWithIndexOffset.ts | 2 +- src/libs/migrateOnyx.ts | 2 + src/libs/migrations/RenameCardIsVirtual.ts | 52 + .../TransactionBackupsToCollection.ts | 2 +- .../index.native.ts | 3 - src/libs/shouldAllowRawHTMLMessages/index.ts | 5 - .../ChatFinderPage/ChatFinderPageFooter.tsx | 11 + .../{SearchPage => ChatFinderPage}/index.tsx | 39 +- src/pages/EditReportFieldText.tsx | 8 +- src/pages/ErrorPage/NotFoundPage.tsx | 5 +- src/pages/NewChatPage.tsx | 429 +++--- .../ManageTeamsExpensesPage.tsx | 2 +- .../PurposeForUsingExpensifyPage.tsx | 14 +- .../BaseOnboardingPersonalDetails.tsx | 3 + .../BaseOnboardingPurpose.tsx | 5 +- .../ReimbursementAccountPage.tsx | 23 +- src/pages/ReportDetailsPage.tsx | 70 +- src/pages/ReportParticipantsPage.tsx | 12 +- src/pages/RoomInvitePage.tsx | 25 +- src/pages/RoomMembersPage.tsx | 6 +- src/pages/Search/SearchPage.tsx | 23 + src/pages/Search/SearchPageBottomTab.tsx | 68 + src/pages/Search/SearchResults.tsx | 17 + .../useCustomBackHandler/index.android.ts | 34 + .../Search/useCustomBackHandler/index.ts | 3 + src/pages/SearchPage/SearchPageFooter.tsx | 19 - src/pages/ValidateLoginPage/index.website.tsx | 10 +- src/pages/WorkspaceSwitcherPage.tsx | 291 ---- .../WorkspacesSectionHeader.tsx | 55 + src/pages/WorkspaceSwitcherPage/index.tsx | 195 +++ src/pages/home/HeaderView.tsx | 12 +- src/pages/home/ReportScreen.tsx | 177 +-- .../report/AnimatedEmptyStateBackground.tsx | 36 +- .../report/ContextMenu/ContextMenuActions.tsx | 24 +- .../report/ReactionList/BaseReactionList.tsx | 1 - .../AttachmentPickerWithMenuItems.tsx | 10 +- .../ReportActionCompose/SuggestionEmoji.tsx | 2 +- .../ReportActionCompose/SuggestionMention.tsx | 22 +- src/pages/home/report/ReportActionItem.tsx | 104 +- .../home/report/ReportActionItemFragment.tsx | 4 +- .../home/report/ReportActionItemMessage.tsx | 2 +- .../report/ReportActionItemParentAction.tsx | 9 +- .../home/report/ReportActionItemSingle.tsx | 4 +- src/pages/home/report/ReportActionsList.tsx | 2 +- .../report/ReportActionsListItemRenderer.tsx | 2 +- src/pages/home/report/ReportActionsView.tsx | 18 +- src/pages/home/report/ReportFooter.tsx | 13 +- src/pages/home/report/ThreadDivider.tsx | 9 +- .../home/report/reportActionPropTypes.js | 2 +- .../withReportAndPrivateNotesOrNotFound.tsx | 6 +- src/pages/home/sidebar/AllSettingsScreen.tsx | 12 +- src/pages/home/sidebar/BottomTabAvatar.tsx | 1 - src/pages/home/sidebar/SidebarLinks.tsx | 5 +- src/pages/home/sidebar/SidebarLinksData.tsx | 16 +- .../FloatingActionButtonAndPopover.tsx | 77 +- src/pages/home/sidebar/SignInButton.tsx | 1 - src/pages/iou/HoldReasonPage.tsx | 4 +- src/pages/iou/SplitBillDetailsPage.tsx | 5 +- src/pages/iou/propTypes/index.js | 14 +- src/pages/iou/request/IOURequestStartPage.tsx | 30 +- ...yForRefactorRequestParticipantsSelector.js | 149 +- .../iou/request/step/IOURequestStepAmount.tsx | 200 ++- .../request/step/IOURequestStepCategory.tsx | 24 +- .../step/IOURequestStepConfirmation.tsx | 156 +- .../request/step/IOURequestStepCurrency.tsx | 23 +- .../iou/request/step/IOURequestStepDate.tsx | 6 +- .../step/IOURequestStepDescription.tsx | 7 +- .../request/step/IOURequestStepDistance.tsx | 124 +- .../step/IOURequestStepDistanceRate.tsx | 112 ++ .../request/step/IOURequestStepMerchant.tsx | 2 +- .../step/IOURequestStepParticipants.tsx | 120 +- .../step/IOURequestStepRoutePropTypes.js | 2 +- .../step/IOURequestStepScan/index.native.tsx | 124 +- .../request/step/IOURequestStepScan/index.tsx | 185 ++- .../request/step/IOURequestStepScan/types.ts | 11 + .../step/IOURequestStepTaxAmountPage.tsx | 43 +- .../step/withFullTransactionOrNotFound.tsx | 4 +- .../step/withWritableReportOrNotFound.tsx | 1 + .../iou/steps/MoneyRequestAmountForm.tsx | 121 +- .../MoneyRequestParticipantsPage.js | 169 --- .../MoneyRequestParticipantsSelector.js | 24 +- src/pages/settings/InitialSettingsPage.tsx | 30 +- .../PersonalDetails/CountrySelectionPage.tsx | 2 +- .../Profile/PersonalDetails/LegalNamePage.tsx | 8 +- src/pages/settings/Profile/ProfilePage.tsx | 7 +- .../settings/Report/ReportSettingsPage.tsx | 5 +- .../Wallet/ActivatePhysicalCardPage.tsx | 2 +- .../Wallet/Card/BaseGetPhysicalCard.tsx | 4 +- .../settings/Wallet/ExpensifyCardPage.tsx | 4 +- .../settings/Wallet/PaymentMethodList.tsx | 8 +- .../Wallet/ReportVirtualCardFraudPage.tsx | 2 +- src/pages/signin/ChooseSSOOrMagicCode.tsx | 101 +- src/pages/signin/SignInPage.tsx | 7 +- src/pages/signin/SignInPageLayout/index.tsx | 4 +- src/pages/tasks/NewTaskDetailsPage.tsx | 22 +- .../AdminPolicyAccessOrNotFoundWrapper.tsx | 7 +- .../FeatureEnabledAccessOrNotFoundWrapper.tsx | 37 +- .../PaidPolicyAccessOrNotFoundWrapper.tsx | 7 +- src/pages/workspace/WorkspaceInitialPage.tsx | 40 +- .../workspace/WorkspaceInviteMessagePage.tsx | 48 +- src/pages/workspace/WorkspaceInvitePage.tsx | 46 +- src/pages/workspace/WorkspaceMembersPage.tsx | 252 ++-- .../workspace/WorkspaceMoreFeaturesPage.tsx | 9 +- .../workspace/WorkspacePageWithSections.tsx | 14 +- .../WorkspaceProfileDescriptionPage.tsx | 1 + src/pages/workspace/WorkspaceProfilePage.tsx | 1 - src/pages/workspace/WorkspacesListPage.tsx | 14 +- src/pages/workspace/WorkspacesListRow.tsx | 197 ++- ...ntingPage.tsx => PolicyAccountingPage.tsx} | 142 +- .../qbo/QuickbooksChartOfAccountsPage.tsx | 7 +- .../accounting/qbo/QuickbooksClassesPage.tsx | 7 +- .../qbo/QuickbooksCustomersPage.tsx | 7 +- .../accounting/qbo/QuickbooksImportPage.tsx | 10 +- .../qbo/QuickbooksLocationsPage.tsx | 7 +- .../accounting/qbo/QuickbooksTaxesPage.tsx | 7 +- .../qboConnectionButton/index.native.tsx | 71 - .../accounting/qboConnectionButton/index.tsx | 29 - .../accounting/qboConnectionButton/types.ts | 14 - .../card/WorkspaceCardCreateAWorkspace.tsx | 2 +- .../categories/WorkspaceCategoriesPage.tsx | 13 +- .../distanceRates/PolicyDistanceRatesPage.tsx | 13 +- .../members/WorkspaceMemberDetailsPage.tsx | 19 +- ...rkspaceMemberDetailsRoleSelectionModal.tsx | 5 +- .../members/WorkspaceOwnerChangeCheck.tsx | 1 + .../members/WorkspaceOwnerChangeErrorPage.tsx | 1 + .../WorkspaceOwnerChangeSuccessPage.tsx | 1 + .../WorkspaceOwnerChangeWrapperPage.tsx | 1 + src/pages/workspace/tags/EditTagPage.tsx | 2 +- src/pages/workspace/tags/TagSettingsPage.tsx | 4 +- .../workspace/tags/WorkspaceEditTagsPage.tsx | 6 +- .../workspace/tags/WorkspaceTagsPage.tsx | 19 +- .../workspace/taxes/WorkspaceTaxesPage.tsx | 13 +- src/pages/workspace/withPolicy.tsx | 14 - .../withPolicyAndFullscreenLoading.tsx | 18 +- src/pages/workspace/withPolicyConnections.tsx | 60 + .../WorkspaceWorkflowsApproverPage.tsx | 49 +- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 40 +- src/styles/index.ts | 1 + src/types/global.d.ts | 1 - src/types/onyx/Card.ts | 1 - src/types/onyx/Modal.ts | 3 + src/types/onyx/OriginalMessage.ts | 50 +- src/types/onyx/Policy.ts | 4 +- .../{PolicyMember.ts => PolicyEmployee.ts} | 8 +- src/types/onyx/Report.ts | 2 +- src/types/onyx/Request.ts | 1 + src/types/onyx/Task.ts | 3 + src/types/onyx/Transaction.ts | 16 +- src/types/onyx/index.ts | 8 +- tests/actions/IOUTest.ts | 66 +- tests/actions/PolicyTest.ts | 16 +- tests/actions/ReportTest.ts | 8 +- tests/e2e/config.ts | 6 +- .../ModifiedExpenseMessage.perf-test.ts | 2 +- tests/perf-test/PolicyUtils.perf-test.ts | 18 +- .../ReportActionCompose.perf-test.tsx | 2 +- tests/perf-test/ReportUtils.perf-test.ts | 2 +- tests/perf-test/SearchPage.perf-test.tsx | 32 +- tests/ui/UnreadIndicatorsTest.tsx | 2 +- tests/unit/IOUUtilsTest.ts | 8 +- tests/unit/MigrationTest.ts | 16 +- tests/unit/ModifiedExpenseMessageTest.ts | 34 +- tests/unit/OptionsListUtilsTest.ts | 9 + tests/unit/ReportActionsUtilsTest.ts | 208 +-- tests/unit/ReportUtilsTest.ts | 20 +- tests/unit/SidebarOrderTest.ts | 18 +- tests/utils/LHNTestUtils.tsx | 2 +- tests/utils/TestHelper.ts | 2 +- tests/utils/collections/policyEmployeeList.ts | 9 + tests/utils/collections/policyMembers.ts | 9 - workflow_tests/mocks/platformDeployMocks.ts | 2 - 338 files changed, 7854 insertions(+), 6581 deletions(-) create mode 100644 .github/scripts/detectRedirectCycle.ts create mode 100755 .github/scripts/verifyRedirect.sh create mode 100644 docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md delete mode 100644 docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md delete mode 100644 docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md create mode 100644 docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md delete mode 100644 patches/@react-native-community+geolocation+3.1.0.patch delete mode 100644 patches/@ua+react-native-airship+15.3.1.patch create mode 100644 patches/react-native+0.73.4+014+fix-inverted-flatlist.patch create mode 100644 patches/react-native-screens+3.30.1+001+fix-screen-type.patch create mode 100644 src/components/ConnectToQuickbooksOnlineButton/index.native.tsx create mode 100644 src/components/ConnectToQuickbooksOnlineButton/index.tsx create mode 100644 src/components/ConnectToQuickbooksOnlineButton/types.ts delete mode 100755 src/components/MoneyTemporaryForRefactorRequestConfirmationList.tsx create mode 100644 src/hooks/useDisableModalDismissOnEscape.ts create mode 100644 src/hooks/useStyledSafeAreaInsets.ts create mode 100644 src/libs/API/parameters/CategorizeTrackedExpenseParams.ts create mode 100644 src/libs/API/parameters/ConnectPolicyToQuickbooksOnlineParams.ts create mode 100644 src/libs/API/parameters/ConvertTrackedExpenseToRequestParams.ts create mode 100644 src/libs/API/parameters/DismissTrackExpenseActionableWhisperParams.ts create mode 100644 src/libs/API/parameters/OpenPolicyAccountingPageParams.ts create mode 100644 src/libs/API/parameters/RemovePolicyConnectionParams.ts create mode 100644 src/libs/API/parameters/ShareTrackedExpenseParams.ts rename src/libs/E2E/tests/{openSearchPageTest.e2e.ts => openChatFinderPageTest.e2e.ts} (82%) delete mode 100644 src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.native.tsx delete mode 100644 src/libs/Navigation/dismissRHP.ts create mode 100644 src/libs/ObjectUtils.ts create mode 100644 src/libs/PolicyEmployeeListUtils.ts delete mode 100644 src/libs/PolicyMembersUtils.ts create mode 100644 src/libs/actions/PolicyConnections.ts create mode 100644 src/libs/actions/applyOnyxUpdatesReliably.ts create mode 100644 src/libs/actions/connections/QuickBooksOnline.ts delete mode 100644 src/libs/actions/connections/getQuickBooksOnlineSetupLink.ts create mode 100644 src/libs/actions/connections/index.ts create mode 100644 src/libs/migrations/RenameCardIsVirtual.ts delete mode 100644 src/libs/shouldAllowRawHTMLMessages/index.native.ts delete mode 100644 src/libs/shouldAllowRawHTMLMessages/index.ts create mode 100644 src/pages/ChatFinderPage/ChatFinderPageFooter.tsx rename src/pages/{SearchPage => ChatFinderPage}/index.tsx (85%) create mode 100644 src/pages/Search/SearchPage.tsx create mode 100644 src/pages/Search/SearchPageBottomTab.tsx create mode 100644 src/pages/Search/SearchResults.tsx create mode 100644 src/pages/Search/useCustomBackHandler/index.android.ts create mode 100644 src/pages/Search/useCustomBackHandler/index.ts delete mode 100644 src/pages/SearchPage/SearchPageFooter.tsx delete mode 100644 src/pages/WorkspaceSwitcherPage.tsx create mode 100644 src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx create mode 100644 src/pages/WorkspaceSwitcherPage/index.tsx create mode 100644 src/pages/iou/request/step/IOURequestStepDistanceRate.tsx delete mode 100644 src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js rename src/pages/workspace/accounting/{WorkspaceAccountingPage.tsx => PolicyAccountingPage.tsx} (71%) delete mode 100644 src/pages/workspace/accounting/qboConnectionButton/index.native.tsx delete mode 100644 src/pages/workspace/accounting/qboConnectionButton/index.tsx delete mode 100644 src/pages/workspace/accounting/qboConnectionButton/types.ts create mode 100644 src/pages/workspace/withPolicyConnections.tsx rename src/types/onyx/{PolicyMember.ts => PolicyEmployee.ts} (73%) create mode 100644 tests/utils/collections/policyEmployeeList.ts delete mode 100644 tests/utils/collections/policyMembers.ts diff --git a/.eslintrc.js b/.eslintrc.js index c2198da60c52..56a5236a7899 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -94,7 +94,6 @@ module.exports = { rules: { 'prefer-regex-literals': 'off', 'rulesdir/no-multiple-onyx-in-file': 'off', - 'rulesdir/onyx-props-must-have-default': 'off', 'react-native-a11y/has-accessibility-hint': ['off'], 'react/jsx-no-constructed-context-values': 'error', 'react-native-a11y/has-valid-accessibility-descriptors': [ diff --git a/.github/scripts/detectRedirectCycle.ts b/.github/scripts/detectRedirectCycle.ts new file mode 100644 index 000000000000..5aa0d1daf342 --- /dev/null +++ b/.github/scripts/detectRedirectCycle.ts @@ -0,0 +1,64 @@ +import {parse} from 'csv-parse'; +import fs from 'fs'; + +const parser = parse(); + +const adjacencyList: Record = {}; +const visited: Map = new Map(); +const backEdges: Map = new Map(); + +function addEdge(source: string, target: string) { + if (!adjacencyList[source]) { + adjacencyList[source] = []; + } + adjacencyList[source].push(target); +} + +function isCyclic(currentNode: string): boolean { + visited.set(currentNode, true); + backEdges.set(currentNode, true); + + // Do a depth first search for all the neighbours. If a node is found in backedge, a cycle is detected. + const neighbours = adjacencyList[currentNode]; + if (neighbours) { + for (const node of neighbours) { + if (!visited.has(node)) { + if (isCyclic(node)) { + return true; + } + } else if (backEdges.has(node)) { + return true; + } + } + } + + backEdges.delete(currentNode); + + return false; +} + +function detectCycle(): boolean { + for (const [node] of Object.entries(adjacencyList)) { + if (!visited.has(node)) { + if (isCyclic(node)) { + const cycle = Array.from(backEdges.keys()); + console.log(`Infinite redirect found in the cycle: ${cycle.join(' -> ')} -> ${node}`); + return true; + } + } + } + return false; +} + +fs.createReadStream(`${process.cwd()}/docs/redirects.csv`) + .pipe(parser) + .on('data', (row) => { + // Create a directed graph of sourceURL -> targetURL + addEdge(row[0], row[1]); + }) + .on('end', () => { + if (detectCycle()) { + process.exit(1); + } + process.exit(0); + }); diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh new file mode 100755 index 000000000000..b8942cd5b23d --- /dev/null +++ b/.github/scripts/verifyRedirect.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# HelpDot - Verifies that redirects.csv does not have any duplicates +# Duplicate sourceURLs break redirection on cloudflare pages + +declare -r REDIRECTS_FILE="docs/redirects.csv" + +declare -r RED='\033[0;31m' +declare -r GREEN='\033[0;32m' +declare -r NC='\033[0m' + +duplicates=$(awk -F, 'a[$1]++{print $1}' $REDIRECTS_FILE) +if [[ -n "$duplicates" ]]; then + echo "${RED}duplicate redirects are not allowed: $duplicates ${NC}" + exit 1 +fi + +npm run detectRedirectCycle +DETECT_CYCLE_EXIT_CODE=$? +if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then + echo -e "${RED}The redirects.csv has a cycle. Please remove the redirect cycle because it will cause an infinite redirect loop ${NC}" + exit 1 +fi + +echo -e "${GREEN}The redirects.csv is valid!${NC}" +exit 0 diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index d4577e112d59..f7f826d66f9b 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -37,6 +37,9 @@ jobs: - name: Create docs routes file run: ./.github/scripts/createDocsRoutes.sh + - name: Check for duplicates and cycles in redirects.csv + run: ./.github/scripts/verifyRedirect.sh + - name: Build with Jekyll uses: actions/jekyll-build-pages@0143c158f4fa0c5dcd99499a5d00859d79f70b0e with: diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index d3aa084b1fcf..10723d5efa04 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -107,7 +107,7 @@ jobs: if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} id: getMergeCommitShaIfUnmergedPR run: | - git merge --allow-unrelated-histories --no-commit ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} + git merge --allow-unrelated-histories -X ours --no-commit ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} git checkout ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} env: GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index d97ea2b86269..bb850e6eda10 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -87,11 +87,12 @@ jobs: MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }} MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} - - name: Run Fastlane production - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane android production - env: - VERSION: ${{ env.VERSION_CODE }} + # Note: Android production deploys are temporarily disabled until https://github.com/Expensify/App/issues/40108 is resolved + # - name: Run Fastlane production + # if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + # run: bundle exec fastlane android production + # env: + # VERSION: ${{ env.VERSION_CODE }} - name: Archive Android sourcemaps uses: actions/upload-artifact@v3 @@ -158,7 +159,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GCP_GEOLOCATION_API_KEY: $${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }} - + - name: Build staging desktop app if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 156b9764bcca..f20939f9df0a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -30,7 +30,7 @@ jobs: # - git diff is used to see the files that were added on this branch # - gh pr view is used to list files touched by this PR. Git diff may give false positives if the branch isn't up-to-date with main # - wc counts the words in the result of the intersection - count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l) + count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'desktop/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' 'web/*.js' 'workflow_tests/*.js' '.github/libs/*.js' '.github/scripts/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l) if [ "$count_new_js" -gt "0" ]; then echo "ERROR: Found new JavaScript files in the project; use TypeScript instead." exit 1 diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index b3adf0f59b9c..a2c7365f7de8 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -32,10 +32,6 @@ "/": "/iou/*", "comment": "I Owe You reports" }, - { - "/": "/request/*", - "comment": "Money request" - }, { "/": "/enable-payments/*", "comment": "Payments setup" @@ -54,11 +50,11 @@ }, { "/": "/split/*", - "comment": "Split Bill" + "comment": "Split Expense" }, { "/": "/request/*", - "comment": "Request Money" + "comment": "Submit Expense" }, { "/": "/new/*", @@ -82,7 +78,7 @@ }, { "/": "/send/*", - "comment": "Send money" + "comment": "Pay someone" }, { "/": "/money2020/*", diff --git a/README.md b/README.md index 026a63606db0..8adadfc9cbe6 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,16 @@ If you want to run the app on an actual physical iOS device, please follow the i ## Running the MacOS desktop app 🖥 * To run the **Development app**, run: `npm run desktop`, this will start a new Electron process running on your MacOS desktop in the `dist/Mac` folder. +## Receiving Notifications +To receive notifications on development build of the app while hitting the Staging or Production API, you need to use the production airship config. +### Android +1. Copy the [production config](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/android/app/src/main/assets/airshipconfig.properties#L1-L7) to the [development config](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/android/app/src/development/assets/airshipconfig.properties#L1-L8). +2. Rebuild the app. + +### iOS +1. Replace the [development key and secret](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/ios/AirshipConfig.plist#L7-L10) with the [production values](https://github.com/Expensify/App/blob/d7c1256f952c0020344d809ee7299b49a4c70db2/ios/AirshipConfig.plist#L11-L14). +2. Rebuild the app. + ## Troubleshooting 1. If you are having issues with **_Getting Started_**, please reference [React Native's Documentation](https://reactnative.dev/docs/environment-setup) 2. If you are running into CORS errors like (in the browser dev console) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1b651bd29def..47803b00c58e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046206 - versionName "1.4.62-6" + versionCode 1001046313 + versionName "1.4.63-13" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md new file mode 100644 index 000000000000..0e195d5e3f1c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/Deposit-Accounts-USD.md @@ -0,0 +1,77 @@ +--- +title: Deposit Accounts - USD +description: How to add a deposit account to receive payments for yourself or your business (US) +--- +# Overview + +There are two types of deposit-only accounts: + +1. If you're an employee seeking reimbursement for expenses you’ve incurred, you’ll add a **Personal deposit-only bank account**. +2. If you're a vendor seeking payment for goods or services, you’ll add a **Business deposit-only account**. + +# How to connect a personal deposit-only bank account + +**Connect a personal deposit-only bank account if you are:** + +- An employee based in the US who gets reimbursed by their employer +- An employee based in Australia who gets reimbursed by their company via batch payments +- An international (non-US) employee whose US-based employers send international reimbursements + +**To establish the connection to a personal bank account, follow these steps:** + +1. Navigate to your **Settings > Account > Payments** and click the **Add Deposit-Only Bank Account** button. +2. Click **Log into your bank** button and click **Continue** on the Plaid connection pop-up window. +3. Search for your bank account in the list of banks and follow the prompts to sign-in to your bank account. +4. Enter your bank login credentials when prompted. + - If your bank doesn't appear, click the 'x' in the upper right corner of the Plaid pop-up window and click **Connect Manually**. + - Enter your account information, then click **Save & Continue**. + +You should be all set! You’ll receive reimbursement for your expense reports directly to this bank account. + +# How to connect a business deposit-only bank account + +**Connect a business deposit-only bank account if you are:** + +- A US-based vendor who wants to be paid directly for bills sent to customers/clients +- A US-based vendor who wants to pay invoices directly via Expensify + +**To establish the connection to a business bank account, follow these steps:** + +1. Navigate to your **Settings > Account > Payments and click the Add Deposit-Only Bank Account** button. +2. Click **Log into your bank** button and click **Continue** on the Plaid connection pop-up window. +3. Search for your bank account in the list of banks and follow the prompts to sign-in to your bank account. +4. Enter your bank login credentials when prompted. + - If your bank doesn't appear, click the 'x' in the upper right corner of the Plaid pop-up window and click **Connect Manually**. + - Enter your account information, then click **Save & Continue**. +5. If you see the option to “Switch to Business” after entering the account owner information, click that link. +6. Enter your Company Name and FEIN or TIN information. +7. Enter your company’s website formatted as https://www.domain.com. + +You should be all set! The bank account will display as a deposit-only business account, and you’ll be paid directly for any invoices you submit for payment. + +# How to delete a deposit-only bank account + +**To delete a deposit-only bank account, do the following:** + +1. Navigate to **Settings > Account > Payments > Bank Accounts** +2. Click the **Delete** next to the bank account you want to remove + +{% include faq-begin.md %} + +## **What happens if my bank requires an additional security check before adding it to a third-party?** + +If your bank account has 2FA enabled or another security step, you should be prompted to complete this when adding the account. If not, and you encounter an error, you can always select the option to “Connect Manually”. Either way, please double check that you are entering the correct bank account details to ensure successful payments. + +## **What if I also want to pay employees with my business bank account?** + +If you’ve added a business deposit-only account and also wish to also pay employees, vendors, or utilize the Expensify Card with this bank account, select “Verify” on the listed bank account. This will take you through the additional verification steps to use this account to issue payments. + +## **I connected my deposit-only bank account – Why haven’t I received my reimbursement?** + +There are a few reasons a reimbursement may be unsuccessful. The first step is to review the estimated deposit date on the report. If it’s after that date and you still haven’t seen the funds, it could have been unsuccessful because: + - The incorrect account was added. If you believe you may have entered the wrong account, please reach out to Concierge and provide the Report ID for the missing reimbursement. + - Your account wasn’t set up for Direct Deposit/ACH. You may want to contact your bank to confirm. + +If you aren’t sure, please reach out to Concierge and we can assist! + +{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md deleted file mode 100644 index 4ae2c669561f..000000000000 --- a/docs/articles/expensify-classic/connect-credit-cards/business-bank-accounts/Business-Bank-Accounts-USD.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Business Bank Accounts - USD -description: How to add/remove Business Bank Accounts (US) ---- -# Overview -Adding a verified business bank account unlocks a myriad of features and automation in Expensify. -Once you connect your business bank account, you can: -- Pay employee expense reports via direct deposit (US) -- Settle company bills via direct transfer -- Accept invoice payments through direct transfer -- Access the Expensify Card - -# How to add a verified business bank account -To connect a business bank account to Expensify, follow the below steps: -1. Go to **Settings > Account > Payments** -2. Click **Add Verified Bank Account** -3. Click **Log into your bank** -4. Click **Continue** -5. When you hit the **Plaid** screen, you'll be shown a list of compatible banks that offer direct online login access -6. Login to the business bank account -- If the bank is not listed, click the X to go back to the connection type -- Here you’ll see the option to **Connect Manually** -- Enter your account and routing numbers -7. Enter your bank login credentials. -- If your bank requires additional security measures, you will be directed to obtain and enter a security code -- If you have more than one account available to choose from, you will be directed to choose the desired account -Next, to verify the bank account, you’ll enter some details about the business as well as some personal information. - -## Enter company information -This is where you’ll add the legal business name as well as several other company details. - -### Company address -The company address must: -- Be located in the US -- Be a physical location -If you input a maildrop address (PO box, UPS Store, etc.), the address will likely be flagged for review and adding the bank account to Expensify will be delayed. - -### Tax Identification Number -This is the identification number that was assigned to the business by the IRS. -### Company website -A company website is required to use most of Expensify’s payment features. When adding the website of the business, format it as, https://www.domain.com. -### Industry Classification Code -You can locate a list of Industry Classification Codes here. -## Enter personal information -Whoever is connecting the bank account to Expensify, must enter their details under the Requestor Information section: -- The address must be a physical address -- The address must be located in the US -- The SSN must be US-issued -This does not need to be a signor on the bank account. If someone other than the Expensify account holder enters their personal information in this section, the details will be flagged for review and adding the bank account to Expensify will be delayed. - -## Upload ID -After entering your personal details, you’ll be prompted to click a link or scan a QR code so that you can do the following: -1. Upload the front and back of your ID -2. Use your device to take a selfie and record a short video of yourself -It’s required that your ID is: -- Issued in the US -- Unexpired - -## Additional Information -Check the appropriate box under **Additional Information**, accept the agreement terms, and verify that all of the information is true and accurate: -- A Beneficial Owner refers to an **individual** who owns 25% or more of the business. -- If you or another **individual** owns 25% or more of the business, please check the appropriate box -- If someone else owns 25% or more of the business, you will be prompted to provide their personal information -If no individual owns more than 25% of the company you do not need to list any beneficial owners. In that case, be sure to leave both boxes unchecked under the Beneficial Owner Additional Information section. - -# How to validate the bank account -The account you set up can be found under **Settings > Account > Payment > Bank Accounts** section in either **Verifying** or **Pending** status. -If it is **Verifying**, then this means we sent you a message and need more information from you. Please check your Concierge chat which should include a message with specific details about what we require to move forward. -If it is **Pending**, then in 1-2 business days Expensify will administer 3 test transactions to your bank account. Please check your Concierge chat for further instructions. If you do not see these test transactions -After these transactions (2 withdrawals and 1 deposit) have been processed in your account, visit your Expensify Inbox, where you'll see a prompt to input the transaction amounts. -Once you've finished these steps, your business bank account is ready to use in Expensify! - -# How to share a verified bank account -Only admins with access to the verified bank account can reimburse employees or pay vendor bills. To grant another admin access to the bank account in Expensify, go to **Settings > Account > Payments > Bank Accounts** and click **"Share"**. Enter their email address, and they will receive instructions from us. Please note, they must be a policy admin on a policy you also have access to in order to share the bank account with them. -When a bank account is shared, it must be revalidated with three new microtransactions to ensure the shared admin has access. This process takes 1-2 business days. Once received, the shared admin can enter the transactions via their Expensify account's Inbox tab. - -Note: A report is shared with all individuals with access to the same business bank account in Expensify for audit purposes. - - -# How to remove access to a verified bank account -This step is important when accountants and staff leave your business. -To remove an admin's access to a shared bank account, go to **Settings > Account > Payments > Shared Business Bank Accounts**. -You'll find a list of individuals who have access to the bank account. Next to each user, you'll see the option to Unshare the bank account. - -# How to delete a verified bank account -If you need to delete a bank account from Expensify, run through the following steps: -1. Head to **Settings > Account > Payments** -2. Click the red **Delete** button under the corresponding bank account - -Be cautious, as if it hasn't been shared with someone else, the next user will need to set it up from the beginning. - -If the bank account is set as the settlement account for your Expensify Cards, you’ll need to designate another bank account as your settlement account under **Settings > Domains > Company Cards > Settings** before this account can be deleted. - -# Deep Dive - -## Verified bank account requirements - -To add a business bank account to issue reimbursements via ACH (US), to pay invoices (US) or utilize the Expensify Card: -- You must enter a physical address for yourself, any Beneficial Owner (if one exists), and the business associated with the bank account. We **cannot** accept a PO Box or MailDrop location. -- If you are adding the bank account to Expensify, you must add it from **your** Expensify account settings. -- If you are adding a bank account to Expensify, we are required by law to verify your identity. Part of this process requires you to verify a US issued photo ID. For utilizing features related to US ACH, your idea must be issued by the United States. You and any Beneficial Owner (if one exists), must also have a US address -- You must have a valid website for your business to utilize the Expensify Card, or to pay invoices with Expensify. - -## Locked bank account -When you reimburse a report, you authorize Expensify to withdraw the funds from your account. If your bank rejects Expensify’s withdrawal request, your verified bank account is locked until the issue is resolved. - -Withdrawal requests can be rejected due to insufficient funds, or if the bank account has not been enabled for direct debit. -If you need to enable direct debits from your verified bank account, your bank will require the following details: -- The ACH CompanyIDs (1270239450 and 4270239450) -- The ACH Originator Name (Expensify) -To request to unlock the bank account, click **Fix** on your bank account under **Settings > Account > Payments > Bank Accounts**. -This sends a request to our support team to review exactly why the bank account was locked. -Please note, unlocking a bank account can take 4-5 business days to process. - -## Error adding ID to Onfido -Expensify is required by both our sponsor bank and federal law to verify the identity of the individual that is initiating the movement of money. We use Onfido to confirm that the person adding a payment method is genuine and not impersonating someone else. - -If you get a generic error message that indicates, "Something's gone wrong", please go through the following steps: - -1. Ensure you are using either Safari (on iPhone) or Chrome (on Android) as your web browser. -2. Check your browser's permissions to make sure that the camera and microphone settings are set to "Allow" -3. Clear your web cache for Safari (on iPhone) or Chrome (on Android). -4. If using a corporate Wi-Fi network, confirm that your corporate firewall isn't blocking the website. -5. Make sure no other apps are overlapping your screen, such as the Facebook Messenger bubble, while recording the video. -6. On iPhone, if using iOS version 15 or later, disable the Hide IP address feature in Safari. -7. If possible, try these steps on another device -8. If you have another phone available, try to follow these steps on that device -If the issue persists, please contact your Account Manager or Concierge for further troubleshooting assistance. - -{% include faq-begin.md %} -## What is a Beneficial Owner? - -A Beneficial Owner refers to an **individual** who owns 25% or more of the business. If no individual owns 25% or more of the business, the company does not have a Beneficial Owner. - - -## What do I do if the Beneficial Owner section only asks for personal details, but our business is owned by another company? - - -Please only indicate you have a Beneficial Owner, if it is an individual that owns 25% or more of the business. - -## Why can’t I input my address or upload my ID? - - -Are you entering a US address? When adding a verified business bank account in Expensify, the individual adding the account, and any beneficial owner (if one exists) are required to have a US address, US photo ID, and a US SSN. If you do not meet these requirements, you’ll need to have another admin add the bank account, and then share access with you once verified. - - -## Why am I being asked for documentation when adding my bank account? -When a bank account is added to Expensify, we complete a series of checks to verify the information provided to us. We conduct these checks to comply with both our sponsor bank's requirements and federal government regulations, specifically the Bank Secrecy Act / Anti-Money Laundering (BSA / AML) laws. Expensify also has anti-fraud measures in place. -If automatic verification fails, we may request manual verification, which could involve documents such as address verification for your business, a letter from your bank confirming bank account ownership, etc. - -If you have any questions regarding the documentation request you received, please contact Concierge and they will be happy to assist. - - -## I don’t see all three microtransactions I need to validate my bank account. What should I do? - -It's a good idea to wait till the end of that second business day. If you still don’t see them, please reach out to your bank and ask them to whitelist our ACH ID's **1270239450** and **4270239450**. Expensify’s ACH Originator Name is "Expensify". - -Make sure to reach out to your Account Manager or to Concierge once you have done so and our team will be able to re-trigger those 3 transactions! - - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md b/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md deleted file mode 100644 index 6114e98883e0..000000000000 --- a/docs/articles/expensify-classic/connect-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Deposit Accounts (AUD) -description: Expensify allows you to add a personal bank account to receive reimbursements for your expenses. We never take money out of this account — it is only a place for us to deposit funds from your employer. This article covers deposit accounts for Australian banks. ---- - -## How-to add your Australian personal deposit account information -1. Confirm with your Policy Admin that they’ve set up Global Reimbursment -2. Set your default policy (by selecting the correct policy after clicking on your profile picture) before adding your deposit account. -3. Go to **Settings > Account > Payments** and click **Add Deposit-Only Bank Account** -![Click the Add Deposit-Only Bank Account button](https://help.expensify.com/assets/images/add-australian-deposit-only-account.png){:width="100%"} - -4. Enter your BSB, account number and name. If your screen looks different than the image below, that means your company hasn't enabled reimbursements through Expensify. Please contact your administrator and ask them to enable reimbursements. - -![Fill in the required fields](https://help.expensify.com/assets/images/add-australian-deposit-only-account-modal.png){:width="100%"} - -# How-to delete a bank account -Bank accounts are easy to delete! Simply click the red **Delete** button in the bank account under **Settings > Account > Payments**. - -![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} - -You can complete this process on a computer or on the mobile app. - diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md index e94d915e4dfa..07421553aeb2 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md @@ -17,7 +17,7 @@ Expensify's direct integration with Zenefits will automatically: - Every employee record in Zenefits must have a work email address since we use this as the unique identifier in Expensify. - Zenefits will add all your employees to one Expensify workspace. If your company uses multiple Expensify workspaces, you'll be given the option to choose which workspace to connect to when you're setting up the integration. -## To connect your Expensify workspace to Gusto: +## To connect your Expensify workspace to Zenefits: 1. Navigate to **Settings > Workspaces > Group > _[Workspace Name]_ > Connections** 2. Scroll down to HR Integrations, click the **Connect to Zenefits** radio button, then click **Sync with Zenefits** diff --git a/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md b/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md index 29fbc8b46323..6a6f99fa398f 100644 --- a/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md +++ b/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md @@ -20,6 +20,7 @@ To change the roles and permissions for members of your workspace, | Approve workspace reports | Only reports submitted to them | Yes | Yes | | Edit workspace settings | No | No | Yes | +{:start="7"} 7. If your workspace uses Advanced Approvals, select an “Approves to.” This determines who the member’s reports must be approved by, if applicable. If “no one” is selected, then any one with the Auditor or Workspace Admin role can approve the member’s reports. 8. Click **Save**. diff --git a/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md b/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md new file mode 100644 index 000000000000..7c3d8077c14d --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md @@ -0,0 +1,111 @@ +--- +title: Enable and set up expense violations +description: Set up rules for expenses and enable violations +--- +
+ +Expensify automatically detects expense errors or discrepancies as violations that must be corrected. You can also set rules for a workspace that will trigger a violation if the rule is not met. These rules can be set for categories, tags, and even for specific domain groups. + +When reviewing submitted expense reports, approvers will see violations highlighted with an exclamation mark. There are two types of violations: +- **Yellow**: Automated highlights that require attention but may not require corrective action. For example, if a receipt was SmartScanned and then the amount was modified, a yellow violation will be added to call out the change for review. +- **Red**: Violations directly tied to your workspace settings. These violations must be addressed before the report can be submitted and reimbursed. + +You can hover over the icon to see a brief description, and you can find more detailed information below the list of expenses. + +{% include info.html %} +If your workspace has automations set to automatically submit reports for approval, the report that contains violations will not be submitted automatically until the violations are corrected. (However, if a comment is added to an expense, it will override the violation as the member is providing a reason for submission *unless* domain workspace rules are set to be strictly enforced, as detailed near the bottom of this article.) +{% include end-info.html %} + +# Enable or disable expense violations + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Expenses** tab on the left. +5. Click the “Enable violations” toggle. +6. If desired, enter the expense rules that will be used to create violations: + - **Max expense age (days)**: How old an expense can be + - **Max expense amount**: How much a single expense can cost + - **Receipt required amount**: How much a single expense can cost before a receipt is required + +{% include info.html %} +Expensify includes certain system mandatory violations that can't be disabled, even if your policy has violations turned off. +{% include end-info.html %} + +# Set category rules + +Admins on a Control workspace can enable specific rules for each category, including setting expense caps for specific categories, requiring receipts, and more. These rules can allow you to have a default expense limit of $2,500 but to only allow a daily entertainment limit of $150 per person. You can also choose to not require receipts for mileage or per diem expenses. + +To set up category rules, +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Click **Edit** to the right of the category. +6. Enter your category rules, as desired: + - **GL Code and Payroll Code**: You can add general ledger (GL) or payroll codes to the category for accounting. GL codes populate automatically if you have an accounting integration connected with Expensify. + - **Max Amount**: You can set specific expense caps for the expense category. Use the Limit Type dropdown to determine if the amount is set per individual expense or per day, then enter the maximum amount into this field. + - **Receipts**: You can determine whether receipts are required for the category. For example, many companies disable receipt requirements for toll expenses. + - **Description**: You can determine whether a description is required for expenses under this category. + - **Description Hint**: You can add a hint in the description field to prompt the expense creator on what they should enter into the description field for expenses under this category. + - **Approver**: You can set a specific approver for expenses labeled with this category. + +If users are in violation of these rules, the violations will be shown in red on the report. + +{% include info.html %} +If Scheduled Submit is enabled on a workspace, expenses with category violations will not be auto-submitted unless the expense has a comment added. +{% include end-info.html %} + +# Make categories required + +This means all expenses must be coded with a Category. + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Categories** tab on the left. +5. Enable the “People must categorize expenses” toggle. + +Each Workspace Member will now be required to select a category for their expense. If they do not select a category, the report will receive a violation, which can prevent submission if Scheduled Submit is enabled. + +# Make tags required + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Tags** tab on the left. +5. Enable the “People must tag expenses” toggle. + +Each Workspace Member will now be required to select a tag for their expense before they’re able to submit it. + +# Require strict compliance by domain group + +You can require strict compliance to require members of a specific domain group to submit reports that meet **all** workspace rules before they can submit their expense report—even if they add a note. Every rule and regulation on the workspace must be met before a report can be submitted. + +{% include info.html %} +This will prevent members from submitting any reports where a manager has granted them a manual exception for any of the workspace rules. +{% include end-info.html %} + +To enable strict domain group compliance for reports, + +1. Hover over Settings, then click **Domains**. +2. Click the **Groups** tab on the left. +3. Click **Edit** to the right of the desired workspace name. +4. Enable the “Strictly enforce expense workspace rules” toggle. + +# FAQs + +**Why can’t my employees see the categories on their expenses?** + +The employee may have their default workspace set as their personal workspace. Look under the details section on top right of the report to ensure it is being reported under the correct workspace. + +**Will the account numbers from our accounting system (QuickBooks Online, Sage Intacct, etc.) show in the category list for employees?** + +The general ledger (GL) account numbers are visible only for Workspace Admins in the workspace settings when they are part of a control workspace. This information is not visible to other members of the workspace. However, if you wish to have this information available to your employees when they are categorizing their expenses, you can edit the account name in your accounting software to include the GL number (for example, Accounts Payable - 12345). + +**What causes a category violation?** + +- An expense is categorized with a category that is not included in the workspace's categories. This may happen if the employee creates an expense under the wrong workspace, which will cause a "category out of workspace" violation. +- If the workspace categories are being imported from an accounting integration and they’ve been updated in the accounting system but not in Expensify, this can cause an old category to still be in use on an open report which would throw a violation on submission. Simply reselect a proper category to clear violation. + +
diff --git a/docs/redirects.csv b/docs/redirects.csv index 51c8c7515e10..95404c2326a0 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -87,6 +87,7 @@ https://help.expensify.com/articles/new-expensify/payments/Request-Money,https:/ https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/tax-tracking,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Apply-Tax https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/User-Roles.html,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Owner,https://help.expensify.com/articles/expensify-classic/workspaces/Assign-billing-owner-and-payment-account +https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Owner.html,https://help.expensify.com/articles/expensify-classic/workspaces/Assign-billing-owner-and-payment-account https://help.expensify.com/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.html,https://help.expensify.com/articles/expensify-classic/spending-insights/Other-Export-Options https://help.expensify.com/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/Global-Reimbursements https://help.expensify.com/expensify-classic/hubs/bank-accounts-and-credit-cards,https://help.expensify.com/expensify-classic/hubs/ @@ -123,7 +124,6 @@ https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/ https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt,https://help.expensify.com/articles/expensify-classic/expensify-billing/Tax-Exempt https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approving-Reports,https://help.expensify.com/expensify-classic/hubs/reports/ https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members,https://help.expensify.com/articles/expensify-classic/workspaces/Invite-members-and-assign-roles -https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Attendee-Tracking,https://help.expensify.com/articles/expensify-classic/expenses/Track-group-expenses https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/expense-and-report-features/Expense-Rules,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules @@ -152,10 +152,11 @@ https://help.expensify.com/articles/expensify-classic/manage-employees-and-repor https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Invite-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Invite-Members https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/Removing-Members,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Removing-Members https://help.expensify.com/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/ -https://help.expensify.com/articles/expensify-classic/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Reimbursing-Reports https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO,https://help.expensify.com/articles/expensify-classic/settings/Enable-two-factor-authentication https://help.expensify.com/articles/expensify-classic/workspaces/Budgets,https://help.expensify.com/articles/expensify-classic/workspaces/Set-budgets https://help.expensify.com/articles/expensify-classic/workspaces/Categories,https://help.expensify.com/articles/expensify-classic/workspaces/Create-categories https://help.expensify.com/articles/expensify-classic/workspaces/Tags,https://help.expensify.com/articles/expensify-classic/workspaces/Create-tags https://help.expensify.com/expensify-classic/hubs/manage-employees-and-report-approvals,https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Approval-Workflows +https://help.expensify.com/articles/expensify-classic/reports/Report-Audit-Log-and-Comments,https://help.expensify.com/articles/expensify-classic/reports/Print-or-download-a-report +https://help.expensify.com/articles/expensify-classic/reports/The-Reports-Page,https://help.expensify.com/articles/expensify-classic/reports/Report-statuses diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 8b911fa849cd..b224296ed75a 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -6,7 +6,7 @@ layout: {% assign pages = site.html_pages | where_exp:'doc','doc.sitemap != false' | where_exp:'doc','doc.url != "/404.html"' %} {% for page in pages %} - {{ page.url | replace:'/index.html','/' | absolute_url | xml_escape }} + {{ page.url | replace:'/index.html','/' | absolute_url | xml_escape | replace:'.html','' }} {% endfor %} \ No newline at end of file diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 9d09b4dc04f2..7f50db5da85a 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -626,9 +626,7 @@ "${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipAutomationResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipCoreResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", @@ -641,9 +639,7 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipAutomationResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipCoreResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", @@ -793,9 +789,7 @@ "${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipAutomationResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipCoreResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipExtendedActionsResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipMessageCenterResources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Airship/AirshipPreferenceCenterResources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", @@ -808,9 +802,7 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipAutomationResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipCoreResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipExtendedActionsResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipMessageCenterResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AirshipPreferenceCenterResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f0bb2ace4f4b..1df39b44ddbc 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.62 + 1.4.63 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.62.6 + 1.4.63.13 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index b8c2140b8546..a89a6217c181 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.62 + 1.4.63 CFBundleSignature ???? CFBundleVersion - 1.4.62.6 + 1.4.63.13 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 0d7c6d12f53c..28a707e6771f 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.62 + 1.4.63 CFBundleVersion - 1.4.62.6 + 1.4.63.13 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f9244c515a2c..f564bfd931e4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,25 +1,24 @@ PODS: - - Airship (16.12.1): - - Airship/Automation (= 16.12.1) - - Airship/Basement (= 16.12.1) - - Airship/Core (= 16.12.1) - - Airship/ExtendedActions (= 16.12.1) - - Airship/MessageCenter (= 16.12.1) - - Airship/Automation (16.12.1): + - Airship (17.7.3): + - Airship/Automation (= 17.7.3) + - Airship/Basement (= 17.7.3) + - Airship/Core (= 17.7.3) + - Airship/FeatureFlags (= 17.7.3) + - Airship/MessageCenter (= 17.7.3) + - Airship/PreferenceCenter (= 17.7.3) + - Airship/Automation (17.7.3): - Airship/Core - - Airship/Basement (16.12.1) - - Airship/Core (16.12.1): + - Airship/Basement (17.7.3) + - Airship/Core (17.7.3): - Airship/Basement - - Airship/ExtendedActions (16.12.1): + - Airship/FeatureFlags (17.7.3): - Airship/Core - - Airship/MessageCenter (16.12.1): + - Airship/MessageCenter (17.7.3): - Airship/Core - - Airship/PreferenceCenter (16.12.1): + - Airship/PreferenceCenter (17.7.3): - Airship/Core - - AirshipFrameworkProxy (2.1.1): - - Airship (= 16.12.1) - - Airship/MessageCenter (= 16.12.1) - - Airship/PreferenceCenter (= 16.12.1) + - AirshipFrameworkProxy (5.1.1): + - Airship (= 17.7.3) - AirshipServiceExtension (17.8.0) - AppAuth (1.6.2): - AppAuth/Core (= 1.6.2) @@ -1162,8 +1161,8 @@ PODS: - React-Mapbuffer (0.73.4): - glog - React-debug - - react-native-airship (15.3.1): - - AirshipFrameworkProxy (= 2.1.1) + - react-native-airship (17.2.1): + - AirshipFrameworkProxy (= 5.1.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1237,7 +1236,7 @@ PODS: - React-Codegen - React-Core - ReactCommon/turbomodule/core - - react-native-geolocation (3.0.6): + - react-native-geolocation (3.2.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1836,7 +1835,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.47): + - RNLiveMarkdown (0.1.62): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1854,9 +1853,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.47) + - RNLiveMarkdown/common (= 0.1.62) - Yoga - - RNLiveMarkdown/common (0.1.47): + - RNLiveMarkdown/common (0.1.62): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2438,8 +2437,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Airship: 2f4510b497a8200780752a5e0304a9072bfffb6d - AirshipFrameworkProxy: ea1b6c665c798637b93c465b5e505be3011f1d9d + Airship: 5a6d3f8a982398940b0d48423bb9b8736717c123 + AirshipFrameworkProxy: 7255f4ed9836dc2920f2f1ea5657ced4cee8a35c AirshipServiceExtension: 0a5fb14c3fd1879355ab05a81d10f64512a4f79c AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 boost: d3f49c53809116a5d38da093a8aa78bf551aed09 @@ -2508,12 +2507,12 @@ SPEC CHECKSUMS: React-jsitracing: e8a2dafb9878dbcad02b6b2b88e66267fb427b74 React-logger: 0a57b68dd2aec7ff738195f081f0520724b35dab React-Mapbuffer: 63913773ed7f96b814a2521e13e6d010282096ad - react-native-airship: 2ed75ff2278f11ff1c1ab08ed68f5bf02727b971 + react-native-airship: 6ab7a7974d53f92b0c46548fc198f797fdbf371f react-native-blob-util: a3ee23cfdde79c769c138d505670055de233b07a react-native-cameraroll: 95ce0d1a7d2d1fe55bf627ab806b64de6c3e69e9 react-native-config: 5ce986133b07fc258828b20b9506de0e683efc1c react-native-document-picker: 8532b8af7c2c930f9e202aac484ac785b0f4f809 - react-native-geolocation: dcc37809bc117ffdb5946fecc127d62319ccd4a9 + react-native-geolocation: c1c21a8cda4abae6724a322458f64ac6889b8c2b react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 74d18ad516037536c2f671ef0914bcce7739b2f5 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d @@ -2565,7 +2564,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 1190c218cdaaf029ee1437076a3fbbc3297d89fb RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: f172c7199283dc9d21bccf7e21ea10741fd19e1d + RNLiveMarkdown: 47dfb50244f9ba1caefbc0efc6404ba41bf6620a RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 3e273e0e867a079ec33df9ee33bb0482434b897d RNPermissions: 8990fc2c10da3640938e6db1647cb6416095b729 @@ -2582,7 +2581,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 3033e0dd5272d46e97bcb406adea4ae0e6907abf - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: a25a81f2b50270f0c0bd0aff2e2ebe4d0b4ec06d diff --git a/package-lock.json b/package-lock.json index 289880189422..d187caddde42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "1.4.62-6", + "version": "1.4.63-13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.62-6", + "version": "1.4.63-13", "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.47", + "@expensify/react-native-live-markdown": "0.1.62", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -28,7 +28,7 @@ "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/geolocation": "^3.0.6", + "@react-native-community/geolocation": "3.2.1", "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", @@ -47,7 +47,7 @@ "@storybook/cli": "^8.0.6", "@storybook/react": "^8.0.6", "@storybook/theming": "^8.0.6", - "@ua/react-native-airship": "^15.3.1", + "@ua/react-native-airship": "17.2.1", "@vue/preload-webpack-plugin": "^2.0.0", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", @@ -57,7 +57,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -101,7 +101,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.23", + "react-native-onyx": "2.0.27", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -201,13 +201,14 @@ "concurrently": "^8.2.2", "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", + "csv-parse": "^5.5.5", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", "electron": "^29.2.0", "electron-builder": "24.13.2", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.44", + "eslint-config-expensify": "^2.0.47", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^24.1.0", @@ -3570,9 +3571,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.47", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.47.tgz", - "integrity": "sha512-zUfwgg6qq47MnGuynamDpdHSlBYwVKFV4Zc/2wlVzFcBndQOjOyFu04Ns8YDB4Gl80LyGvfAuBT/sU+kvmMU6g==", + "version": "0.1.62", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.62.tgz", + "integrity": "sha512-o70/tFIGZJ1U8U8aqTQu1HAZed6nt5LYWk74mrceRxQHOqsKhZgn2q5EuEy8EMIcnCGKjwxuDyZJbuRexgHx/A==", "engines": { "node": ">= 18.0.0" }, @@ -8790,8 +8791,12 @@ "license": "MIT" }, "node_modules/@react-native-community/geolocation": { - "version": "3.0.6", - "license": "MIT", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.2.1.tgz", + "integrity": "sha512-/+HNzuRl4UCMma7KK+KYL8k2nxAGuW+DGxqmqfpiqKBlCkCUbuFHaZZdqVD6jpsn9r/ghe583ECLmd9SV9I4Bw==", + "engines": { + "node": ">=18.0.0" + }, "peerDependencies": { "react": "*", "react-native": "*" @@ -13336,8 +13341,9 @@ } }, "node_modules/@ua/react-native-airship": { - "version": "15.3.1", - "license": "Apache-2.0", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-17.2.1.tgz", + "integrity": "sha512-+C5fuPU4MMEpN7I5NbrR8F8awPyaHC732ONxMAZhrjVbfNVuZlpCwptz1xmiRkfiH/nzxhF5uvf+CiOKVYamPQ==", "engines": { "node": ">= 16.0.0" }, @@ -16457,10 +16463,8 @@ }, "node_modules/classnames": { "version": "2.5.0", - "license": "MIT", - "workspaces": [ - "benchmarks" - ] + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz", + "integrity": "sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA==" }, "node_modules/clean-css": { "version": "5.3.2", @@ -16546,7 +16550,8 @@ }, "node_modules/clipboard": { "version": "2.0.11", - "license": "MIT", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", "dependencies": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -17736,6 +17741,12 @@ "version": "3.1.1", "license": "MIT" }, + "node_modules/csv-parse": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.5.tgz", + "integrity": "sha512-erCk7tyU3yLWAhk6wvKxnyPtftuy/6Ak622gOO7BCJ05+TYffnPCJF905wmOQm+BpkX54OdAl8pveJwUdpnCXQ==", + "dev": true + }, "node_modules/dag-map": { "version": "1.0.2", "license": "MIT" @@ -18053,7 +18064,8 @@ }, "node_modules/delegate": { "version": "3.2.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, "node_modules/delegates": { "version": "1.0.0", @@ -19245,9 +19257,10 @@ } }, "node_modules/eslint-config-expensify": { - "version": "2.0.44", + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.48.tgz", + "integrity": "sha512-PFegJ9Wfsiu5tgevhjA1toCxsZ8Etfk6pIjtXAnwpmVj7q4CtB3QDRusJoUDyJ3HrZr8AsFKViz7CU/CBTfwOw==", "dev": true, - "license": "ISC", "dependencies": { "@lwc/eslint-plugin-lwc": "^1.7.2", "babel-eslint": "^10.1.0", @@ -20207,8 +20220,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", - "integrity": "sha512-/NAZoAXqeqFWHvC61dueqq9VjRugF69urUtDdDhsfvu1sQE2PCnBoM7a+ACoAEWRYrnP82cyHHhdSA8e7fPuAg==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", + "integrity": "sha512-zz0/y0apISP1orxXEQOgn+Uod45O4wVypwwtaqcDPV4dH1tC3i4L98NoLSZvLn7Y17EcceSkfN6QCEsscgFTDQ==", "license": "MIT", "dependencies": { "classnames": "2.5.0", @@ -20261,6 +20274,8 @@ }, "node_modules/expensify-common/node_modules/ua-parser-js": { "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", "funding": [ { "type": "opencollective", @@ -20275,7 +20290,6 @@ "url": "https://github.com/sponsors/faisalman" } ], - "license": "MIT", "engines": { "node": "*" } @@ -21723,7 +21737,8 @@ }, "node_modules/good-listener": { "version": "1.2.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", "dependencies": { "delegate": "^3.1.2" } @@ -22774,7 +22789,8 @@ }, "node_modules/immediate": { "version": "3.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -26833,7 +26849,8 @@ }, "node_modules/lie": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", "dependencies": { "immediate": "~3.0.5" } @@ -26976,7 +26993,8 @@ }, "node_modules/localforage": { "version": "1.10.0", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", "dependencies": { "lie": "3.1.1" } @@ -31336,9 +31354,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.23.tgz", - "integrity": "sha512-A0SuipCwAswl8+hBlr9tNPTBdxixKBJkcdP8YpgUC072/4Kcfvv0pfpfQOHoeCR/bP2wvDagpdN3VtebRfoVMg==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.27.tgz", + "integrity": "sha512-mNtXmJ2r7UwEym2J7Tu09M42QoxIhwEdiGYDw9v26wp/kQCJChKTP0yUrp8QdPKkcwywRFPVlNxt3Rx8Mp0hFg==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -33306,7 +33324,8 @@ }, "node_modules/select": { "version": "1.1.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" }, "node_modules/select-hose": { "version": "2.0.0", diff --git a/package.json b/package.json index 9e2846f469d9..1e2e66a1b73b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.62-6", + "version": "1.4.63-13", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -28,6 +28,7 @@ "desktop-build": "scripts/build-desktop.sh production", "desktop-build-staging": "scripts/build-desktop.sh staging", "createDocsRoutes": "ts-node .github/scripts/createDocsRoutes.ts", + "detectRedirectCycle": "ts-node .github/scripts/detectRedirectCycle.ts", "desktop-build-adhoc": "scripts/build-desktop.sh adhoc", "ios-build": "fastlane ios build", "android-build": "fastlane android build", @@ -64,7 +65,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.47", + "@expensify/react-native-live-markdown": "0.1.62", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -79,7 +80,7 @@ "@react-native-async-storage/async-storage": "1.21.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/geolocation": "^3.0.6", + "@react-native-community/geolocation": "3.2.1", "@react-native-community/netinfo": "11.2.1", "@react-native-firebase/analytics": "^12.3.0", "@react-native-firebase/app": "^12.3.0", @@ -98,7 +99,7 @@ "@storybook/cli": "^8.0.6", "@storybook/react": "^8.0.6", "@storybook/theming": "^8.0.6", - "@ua/react-native-airship": "^15.3.1", + "@ua/react-native-airship": "17.2.1", "@vue/preload-webpack-plugin": "^2.0.0", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", @@ -108,7 +109,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#13de5b0606662df33fa1392ad82cc11daadff52e", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#c0f7f3b6558fbeda0527c80d68460d418afef219", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -152,7 +153,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.23", + "react-native-onyx": "2.0.27", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -252,13 +253,14 @@ "concurrently": "^8.2.2", "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", + "csv-parse": "^5.5.5", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", "electron": "^29.2.0", "electron-builder": "24.13.2", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.44", + "eslint-config-expensify": "^2.0.47", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^24.1.0", diff --git a/patches/@react-native-community+geolocation+3.1.0.patch b/patches/@react-native-community+geolocation+3.1.0.patch deleted file mode 100644 index 5afa0b8e0897..000000000000 --- a/patches/@react-native-community+geolocation+3.1.0.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git a/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec b/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec -index a319e73..c1ea11c 100644 ---- a/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec -+++ b/node_modules/@react-native-community/geolocation/react-native-geolocation.podspec -@@ -1,8 +1,6 @@ - require 'json' - - package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) --folly_version = '2021.07.22.00' --folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' - - Pod::Spec.new do |s| - s.name = "react-native-geolocation" -@@ -17,20 +15,11 @@ Pod::Spec.new do |s| - s.source = { :git => "https://github.com/react-native-community/react-native-geolocation.git", :tag => "v#{s.version}" } - s.source_files = "ios/**/*.{h,m,mm}" - -- s.dependency 'React-Core' - s.frameworks = 'CoreLocation' - -- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then -- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" -- s.pod_target_xcconfig = { -- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", -- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" -- } -- -- s.dependency "React-Codegen" -- s.dependency "RCT-Folly", folly_version -- s.dependency "RCTRequired" -- s.dependency "RCTTypeSafety" -- s.dependency "ReactCommon/turbomodule/core" -+ if defined?(install_modules_dependencies()) != nil -+ install_modules_dependencies(s) -+ else -+ s.dependency "React-Core" - end - end diff --git a/patches/@ua+react-native-airship+15.3.1.patch b/patches/@ua+react-native-airship+15.3.1.patch deleted file mode 100644 index 1ab11c1f7444..000000000000 --- a/patches/@ua+react-native-airship+15.3.1.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/node_modules/@ua/react-native-airship/react-native-airship.podspec b/node_modules/@ua/react-native-airship/react-native-airship.podspec -index 5e0ce2d..4456f61 100644 ---- a/node_modules/@ua/react-native-airship/react-native-airship.podspec -+++ b/node_modules/@ua/react-native-airship/react-native-airship.podspec -@@ -20,18 +20,7 @@ Pod::Spec.new do |s| - - # Don't install the dependencies when we run `pod install` in the old architecture. - if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then -- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" -- s.pod_target_xcconfig = { -- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", -- "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", -- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" -- } -- s.dependency "React-Codegen" -- s.dependency "React-RCTFabric" -- s.dependency "RCT-Folly" -- s.dependency "RCTRequired" -- s.dependency "RCTTypeSafety" -- s.dependency "ReactCommon/turbomodule/core" -+ install_modules_dependencies(s) - end - - diff --git a/patches/react-native+0.73.4+014+fix-inverted-flatlist.patch b/patches/react-native+0.73.4+014+fix-inverted-flatlist.patch new file mode 100644 index 000000000000..7bed06d01913 --- /dev/null +++ b/patches/react-native+0.73.4+014+fix-inverted-flatlist.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp +index a8ecce5..6ad790e 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp ++++ b/node_modules/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp +@@ -66,7 +66,17 @@ void ScrollViewShadowNode::layout(LayoutContext layoutContext) { + Point ScrollViewShadowNode::getContentOriginOffset() const { + auto stateData = getStateData(); + auto contentOffset = stateData.contentOffset; +- return {-contentOffset.x, -contentOffset.y + stateData.scrollAwayPaddingTop}; ++ auto props = getConcreteProps(); ++ ++ float productX = 1.0f; ++ float productY = 1.0f; ++ ++ for (const auto& operation : props.transform.operations) { ++ productX *= operation.x; ++ productY *= operation.y; ++ } ++ ++ return {-contentOffset.x * productX, (-contentOffset.y + stateData.scrollAwayPaddingTop) * productY}; + } + + } // namespace facebook::react diff --git a/patches/react-native-screens+3.30.1+001+fix-screen-type.patch b/patches/react-native-screens+3.30.1+001+fix-screen-type.patch new file mode 100644 index 000000000000..f282ec58b07b --- /dev/null +++ b/patches/react-native-screens+3.30.1+001+fix-screen-type.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/react-native-screens/src/components/Screen.tsx b/node_modules/react-native-screens/src/components/Screen.tsx +index 3f9a1cb..45767f7 100644 +--- a/node_modules/react-native-screens/src/components/Screen.tsx ++++ b/node_modules/react-native-screens/src/components/Screen.tsx +@@ -79,6 +79,7 @@ export class InnerScreen extends React.Component { + // Due to how Yoga resolves layout, we need to have different components for modal nad non-modal screens + const AnimatedScreen = + Platform.OS === 'android' || ++ stackPresentation === undefined || + stackPresentation === 'push' || + stackPresentation === 'containedModal' || + stackPresentation === 'containedTransparentModal' diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 76ea18d37d5f..9ed4242d7604 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -48,6 +48,7 @@ export default { EXPENSIFY: { // Note: This will be EXACTLY what is set for EXPENSIFY_URL whether the proxy is enabled or not. EXPENSIFY_URL: expensifyURL, + SECURE_EXPENSIFY_URL: secureExpensifyUrl, NEW_EXPENSIFY_URL: newExpensifyURL, // The DEFAULT API is the API used by most environments, except staging, where we use STAGING (defined below) @@ -72,7 +73,7 @@ export default { IS_USING_LOCAL_WEB: useNgrok || expensifyURLRoot.includes('dev'), PUSHER: { APP_KEY: get(Config, 'PUSHER_APP_KEY', '268df511a204fbb60884'), - SUFFIX: get(Config, 'PUSHER_DEV_SUFFIX', ''), + SUFFIX: ENVIRONMENT === CONST.ENVIRONMENT.DEV ? get(Config, 'PUSHER_DEV_SUFFIX', '') : '', CLUSTER: 'mt1', }, SITE_TITLE: 'New Expensify', diff --git a/src/CONST.ts b/src/CONST.ts index 74e722cdba59..2cd614b74816 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6,9 +6,10 @@ import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; +import type {Unit} from './types/onyx/Policy'; type RateAndUnit = { - unit: string; + unit: Unit; rate: number; }; type CurrencyDefaultMileageRate = Record; @@ -633,58 +634,59 @@ const CONST = { LIMIT: 50, // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts TYPE: { - ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER', - ADDCOMMENT: 'ADDCOMMENT', - ACTIONABLEJOINREQUEST: 'ACTIONABLEJOINREQUEST', + ACTIONABLE_JOIN_REQUEST: 'ACTIONABLEJOINREQUEST', + ACTIONABLE_MENTION_WHISPER: 'ACTIONABLEMENTIONWHISPER', + ACTIONABLE_TRACK_EXPENSE_WHISPER: 'ACTIONABLETRACKEXPENSEWHISPER', + ADD_COMMENT: 'ADDCOMMENT', APPROVED: 'APPROVED', - CHANGEFIELD: 'CHANGEFIELD', // OldDot Action - CHANGEPOLICY: 'CHANGEPOLICY', // OldDot Action - CHANGETYPE: 'CHANGETYPE', // OldDot Action - CHRONOSOOOLIST: 'CHRONOSOOOLIST', + CHANGE_FIELD: 'CHANGEFIELD', // OldDot Action + CHANGE_POLICY: 'CHANGEPOLICY', // OldDot Action + CHANGE_TYPE: 'CHANGETYPE', // OldDot Action + CHRONOS_OOO_LIST: 'CHRONOSOOOLIST', CLOSED: 'CLOSED', CREATED: 'CREATED', - DELEGATESUBMIT: 'DELEGATESUBMIT', // OldDot Action - DELETEDACCOUNT: 'DELETEDACCOUNT', // OldDot Action + DELEGATE_SUBMIT: 'DELEGATESUBMIT', // OldDot Action + DELETED_ACCOUNT: 'DELETEDACCOUNT', // OldDot Action DONATION: 'DONATION', // OldDot Action - EXPORTEDTOCSV: 'EXPORTEDTOCSV', // OldDot Action - EXPORTEDTOINTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action - EXPORTEDTOQUICKBOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action + EXPORTED_TO_CSV: 'EXPORTEDTOCSV', // OldDot Action + EXPORTED_TO_INTEGRATION: 'EXPORTEDTOINTEGRATION', // OldDot Action + EXPORTED_TO_QUICK_BOOKS: 'EXPORTEDTOQUICKBOOKS', // OldDot Action FORWARDED: 'FORWARDED', // OldDot Action HOLD: 'HOLD', - HOLDCOMMENT: 'HOLDCOMMENT', + HOLD_COMMENT: 'HOLDCOMMENT', IOU: 'IOU', - INTEGRATIONSMESSAGE: 'INTEGRATIONSMESSAGE', // OldDot Action - MANAGERATTACHRECEIPT: 'MANAGERATTACHRECEIPT', // OldDot Action - MANAGERDETACHRECEIPT: 'MANAGERDETACHRECEIPT', // OldDot Action - MARKEDREIMBURSED: 'MARKEDREIMBURSED', // OldDot Action - MARKREIMBURSEDFROMINTEGRATION: 'MARKREIMBURSEDFROMINTEGRATION', // OldDot Action - MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', + INTEGRATIONS_MESSAGE: 'INTEGRATIONSMESSAGE', // OldDot Action + MANAGER_ATTACH_RECEIPT: 'MANAGERATTACHRECEIPT', // OldDot Action + MANAGER_DETACH_RECEIPT: 'MANAGERDETACHRECEIPT', // OldDot Action + MARKED_REIMBURSED: 'MARKEDREIMBURSED', // OldDot Action + MARK_REIMBURSED_FROM_INTEGRATION: 'MARKREIMBURSEDFROMINTEGRATION', // OldDot Action + MODIFIED_EXPENSE: 'MODIFIEDEXPENSE', MOVED: 'MOVED', - OUTDATEDBANKACCOUNT: 'OUTDATEDBANKACCOUNT', // OldDot Action - REIMBURSEMENTACHBOUNCE: 'REIMBURSEMENTACHBOUNCE', // OldDot Action - REIMBURSEMENTACHCANCELLED: 'REIMBURSEMENTACHCANCELLED', // OldDot Action - REIMBURSEMENTACCOUNTCHANGED: 'REIMBURSEMENTACCOUNTCHANGED', // OldDot Action - REIMBURSEMENTDELAYED: 'REIMBURSEMENTDELAYED', // OldDot Action - REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', - REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', - REIMBURSEMENTREQUESTED: 'REIMBURSEMENTREQUESTED', // OldDot Action - REIMBURSEMENTSETUP: 'REIMBURSEMENTSETUP', // OldDot Action + OUTDATED_BANK_ACCOUNT: 'OUTDATEDBANKACCOUNT', // OldDot Action + REIMBURSEMENT_ACH_BOUNCE: 'REIMBURSEMENTACHBOUNCE', // OldDot Action + REIMBURSEMENT_ACH_CANCELLED: 'REIMBURSEMENTACHCANCELLED', // OldDot Action + REIMBURSEMENT_ACCOUNT_CHANGED: 'REIMBURSEMENTACCOUNTCHANGED', // OldDot Action + REIMBURSEMENT_DELAYED: 'REIMBURSEMENTDELAYED', // OldDot Action + REIMBURSEMENT_QUEUED: 'REIMBURSEMENTQUEUED', + REIMBURSEMENT_DEQUEUED: 'REIMBURSEMENTDEQUEUED', + REIMBURSEMENT_REQUESTED: 'REIMBURSEMENTREQUESTED', // OldDot Action + REIMBURSEMENT_SETUP: 'REIMBURSEMENTSETUP', // OldDot Action RENAMED: 'RENAMED', - REPORTPREVIEW: 'REPORTPREVIEW', - SELECTEDFORRANDOMAUDIT: 'SELECTEDFORRANDOMAUDIT', // OldDot Action + REPORT_PREVIEW: 'REPORTPREVIEW', + SELECTED_FOR_RANDOM_AUDIT: 'SELECTEDFORRANDOMAUDIT', // OldDot Action SHARE: 'SHARE', // OldDot Action - STRIPEPAID: 'STRIPEPAID', // OldDot Action + STRIPE_PAID: 'STRIPEPAID', // OldDot Action SUBMITTED: 'SUBMITTED', - TAKECONTROL: 'TAKECONTROL', // OldDot Action - TASKCANCELLED: 'TASKCANCELLED', - TASKCOMPLETED: 'TASKCOMPLETED', - TASKEDITED: 'TASKEDITED', - TASKREOPENED: 'TASKREOPENED', + TAKE_CONTROL: 'TAKECONTROL', // OldDot Action + TASK_CANCELLED: 'TASKCANCELLED', + TASK_COMPLETED: 'TASKCOMPLETED', + TASK_EDITED: 'TASKEDITED', + TASK_REOPENED: 'TASKREOPENED', UNAPPROVED: 'UNAPPROVED', // OldDot Action UNHOLD: 'UNHOLD', UNSHARE: 'UNSHARE', // OldDot Action - UPDATEGROUPCHATMEMBERROLE: 'UPDATEGROUPCHATMEMBERROLE', - POLICYCHANGELOG: { + UPDATE_GROUP_CHAT_MEMBER_ROLE: 'UPDATEGROUPCHATMEMBERROLE', + POLICY_CHANGE_LOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET', ADD_CATEGORY: 'POLICYCHANGELOG_ADD_CATEGORY', @@ -712,16 +714,16 @@ const CONST = { REMOVE_FROM_ROOM: 'POLICYCHANGELOG_REMOVEFROMROOM', LEAVE_ROOM: 'POLICYCHANGELOG_LEAVEROOM', REPLACE_CATEGORIES: 'POLICYCHANGELOG_REPLACE_CATEGORIES', - SET_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT', + SET_AUTO_REIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT', SET_AUTO_JOIN: 'POLICYCHANGELOG_SET_AUTO_JOIN', SET_CATEGORY_NAME: 'POLICYCHANGELOG_SET_CATEGORY_NAME', SHARED_BUDGET_NOTIFICATION: 'POLICYCHANGELOG_SHARED_BUDGET_NOTIFICATION', UPDATE_ACH_ACCOUNT: 'POLICYCHANGELOG_UPDATE_ACH_ACCOUNT', UPDATE_APPROVER_RULE: 'POLICYCHANGELOG_UPDATE_APPROVER_RULE', UPDATE_AUDIT_RATE: 'POLICYCHANGELOG_UPDATE_AUDIT_RATE', - UPDATE_AUTOHARVESTING: 'POLICYCHANGELOG_UPDATE_AUTOHARVESTING', - UPDATE_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_UPDATE_AUTOREIMBURSEMENT', - UPDATE_AUTOREPORTING_FREQUENCY: 'POLICYCHANGELOG_UPDATE_AUTOREPORTING_FREQUENCY', + UPDATE_AUTO_HARVESTING: 'POLICYCHANGELOG_UPDATE_AUTOHARVESTING', + UPDATE_AUTO_REIMBURSEMENT: 'POLICYCHANGELOG_UPDATE_AUTOREIMBURSEMENT', + UPDATE_AUTO_REPORTING_FREQUENCY: 'POLICYCHANGELOG_UPDATE_AUTOREPORTING_FREQUENCY', UPDATE_BUDGET: 'POLICYCHANGELOG_UPDATE_BUDGET', UPDATE_CATEGORY: 'POLICYCHANGELOG_UPDATE_CATEGORY', UPDATE_CURRENCY: 'POLICYCHANGELOG_UPDATE_CURRENCY', @@ -752,7 +754,7 @@ const CONST = { UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY', }, - ROOMCHANGELOG: { + ROOM_CHANGE_LOG: { INVITE_TO_ROOM: 'INVITETOROOM', REMOVE_FROM_ROOM: 'REMOVEFROMROOM', LEAVE_ROOM: 'LEAVEROOM', @@ -768,6 +770,9 @@ const CONST = { INVITE: 'invited', NOTHING: 'nothing', }, + ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION: { + NOTHING: 'nothing', + }, ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION: { ACCEPT: 'accept', DECLINE: 'decline', @@ -875,7 +880,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - SEARCH_RENDER: 'search_render', + CHAT_FINDER_RENDER: 'search_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', @@ -1211,9 +1216,9 @@ const CONST = { NOT_IMPORTED: 'NOT_IMPORTED', IMPORTED: 'IMPORTED', }, - QUICK_BOOKS_ONLINE: 'quickbooksOnline', + QUICKBOOKS_ONLINE: 'quickbooksOnline', - QUICK_BOOKS_IMPORTS: { + QUICKBOOKS_IMPORTS: { SYNC_CLASSES: 'syncClasses', ENABLE_NEW_CATEGORIES: 'enableNewCategories', SYNC_CUSTOMERS: 'syncCustomers', @@ -1359,7 +1364,7 @@ const CONST = { }, KYC_WALL_SOURCE: { - REPORT: 'REPORT', // The user attempted to pay a money request + REPORT: 'REPORT', // The user attempted to pay an expense ENABLE_WALLET: 'ENABLE_WALLET', // The user clicked on the `Enable wallet` button on the Wallet page TRANSFER_BALANCE: 'TRANSFER_BALANCE', // The user attempted to transfer their wallet balance to their bank account or debit card }, @@ -1395,7 +1400,7 @@ const CONST = { }, IOU: { - // This is the transactionID used when going through the create money request flow so that it mimics a real transaction (like the edit flow) + // This is the transactionID used when going through the create expense flow so that it mimics a real transaction (like the edit flow) OPTIMISTIC_TRANSACTION_ID: '1', // Note: These payment types are used when building IOU reportAction message values in the server and should // not be changed. @@ -1407,6 +1412,9 @@ const CONST = { ACTION: { EDIT: 'edit', CREATE: 'create', + REQUEST: 'request', + CATEGORIZE: 'categorize', + SHARE: 'share', }, DEFAULT_AMOUNT: 0, TYPE: { @@ -1448,6 +1456,11 @@ const CONST = { CANCEL_REASON: { PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED', }, + SHARE: { + ROLE: { + ACCOUNTANT: 'accountant', + }, + }, }, GROWL: { @@ -1601,25 +1614,23 @@ const CONST = { GENERAL_SETTINGS: 'generalSettings', }, CONNECTIONS: { - SYNC_STATUS: { - STARTING: 'starting', - FINISHED: 'finished', - PROGRESS: 'progress', - }, NAME: { // Here we will add other connections names when we add support for them QBO: 'quickbooksOnline', }, SYNC_STAGE_NAME: { STARTING_IMPORT: 'startingImport', - QBO_CUSTOMERS: 'quickbooksOnlineImportCustomers', - QBO_EMPLOYEES: 'quickbooksOnlineImportEmployees', - QBO_ACCOUNTS: 'quickbooksOnlineImportAccounts', - QBO_CLASSES: 'quickbooksOnlineImportClasses', - QBO_LOCATIONS: 'quickbooksOnlineImportLocations', - QBO_PROCESSING: 'quickbooksOnlineImportProcessing', - QBO_PAYMENTS: 'quickbooksOnlineSyncBillPayments', - QBO_TAX_CODES: 'quickbooksOnlineSyncTaxCodes', + QBO_IMPORT_MAIN: 'quickbooksOnlineImportMain', + QBO_IMPORT_CUSTOMERS: 'quickbooksOnlineImportCustomers', + QBO_IMPORT_EMPLOYEES: 'quickbooksOnlineImportEmployees', + QBO_IMPORT_ACCOUNTS: 'quickbooksOnlineImportAccounts', + QBO_IMPORT_CLASSES: 'quickbooksOnlineImportClasses', + QBO_IMPORT_LOCATIONS: 'quickbooksOnlineImportLocations', + QBO_IMPORT_PROCESSING: 'quickbooksOnlineImportProcessing', + QBO_SYNC_PAYMENTS: 'quickbooksOnlineSyncBillPayments', + QBO_IMPORT_TAX_CODES: 'quickbooksOnlineSyncTaxCodes', + QBO_CHECK_CONNECTION: 'quickbooksOnlineCheckConnection', + JOB_DONE: 'jobDone', }, }, }, @@ -3288,6 +3299,13 @@ const CONST = { SCAN: 'scan', DISTANCE: 'distance', }, + TAB_SEARCH: { + ALL: 'all', + SENT: 'sent', + DRAFTS: 'drafts', + WAITING_ON_YOU: 'waitingOnYou', + FINISHED: 'finished', + }, STATUS_TEXT_MAX_LENGTH: 100, DROPDOWN_BUTTON_SIZE: { @@ -3538,12 +3556,11 @@ const CONST = { ONBOARDING_CONCIERGE: { [onboardingChoices.TRACK]: - "# Welcome to Expensify, let's start tracking your expenses!\n" + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + "# Let's start tracking your expenses!\n" + '\n' + "To track your expenses, create a workspace to keep everything in one place. Here's how:\n" + - '1. From the home screen, click the green + button > New Workspace\n' + - '2. Give your workspace a name (e.g. "My business expenses”).\n' + + '1. From the home screen, click the green + button > *New Workspace*\n' + + '2. Give your workspace a name (e.g. "My business expenses").\n' + '\n' + 'Then, add expenses to your workspace:\n' + '1. Find your workspace using the search field.\n' + @@ -3552,30 +3569,27 @@ const CONST = { '\n' + "We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!", [onboardingChoices.EMPLOYER]: - '# Welcome to Expensify, the fastest way to get paid back!\n' + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '# Expensify is the fastest way to get paid back!\n' + '\n' + 'To submit expenses for reimbursement:\n' + - '1. From the home screen, click the green + button > Request money.\n' + + '1. From the home screen, click the green + button > *Request money*.\n' + "2. Enter an amount or scan a receipt, then input your boss's email.\n" + '\n' + "That'll send a request to get you paid back. Let me know if you have any questions!", [onboardingChoices.MANAGE_TEAM]: - "# Welcome to Expensify, let's start managing your team's expenses!\n" + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + "# Let's start managing your team's expenses!\n" + '\n' + "To manage your team's expenses, create a workspace to keep everything in one place. Here's how:\n" + - '1. From the home screen, click the green + button > New Workspace\n' + - '2. Give your workspace a name (e.g. “Sales team expenses”).\n' + + '1. From the home screen, click the green + button > *New Workspace*\n' + + '2. Give your workspace a name (e.g. "Sales team expenses").\n' + '\n' + - 'Then, invite your team to your workspace via the Members pane and connect a business bank account to reimburse them. Let me know if you have any questions!', + 'Then, invite your team to your workspace via the Members pane and [connect a business bank account](https://help.expensify.com/articles/new-expensify/bank-accounts/Connect-a-Bank-Account) to reimburse them. Let me know if you have any questions!', [onboardingChoices.PERSONAL_SPEND]: - "# Welcome to Expensify, let's start tracking your expenses!\n" + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + "# Let's start tracking your expenses! \n" + '\n' + "To track your expenses, create a workspace to keep everything in one place. Here's how:\n" + - '1. From the home screen, click the green + button > New Workspace\n' + - '2. Give your workspace a name (e.g. "My expenses”).\n' + + '1. From the home screen, click the green + button > *New Workspace*\n' + + '2. Give your workspace a name (e.g. "My expenses").\n' + '\n' + 'Then, add expenses to your workspace:\n' + '1. Find your workspace using the search field.\n' + @@ -3584,19 +3598,13 @@ const CONST = { '\n' + "We'll store all expenses in your new workspace for easy access. Let me know if you have any questions!", [onboardingChoices.CHAT_SPLIT]: - '# Welcome to Expensify, where splitting the bill is an easy conversation!\n' + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + + '# Splitting the bill is as easy as a conversation!\n' + '\n' + 'To split an expense:\n' + - '1. From the home screen, click the green + button > Request money.\n' + + '1. From the home screen, click the green + button > *Request money*.\n' + '2. Enter an amount or scan a receipt, then choose who you want to split it with.\n' + '\n' + "We'll send a request to each person so they can pay you back. Let me know if you have any questions!", - [onboardingChoices.LOOKING_AROUND]: - '# Welcome to Expensify!\n' + - "Hi there, I'm Concierge. Chat with me here for anything you need.\n" + - '\n' + - "Expensify is best known for expense and corporate card management, but we do a lot more than that. Let me know what you're interested in and I'll help get you started.", }, REPORT_FIELD_TITLE_FIELD_ID: 'text_title', @@ -4340,8 +4348,10 @@ const CONST = { } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; + type IOUType = ValueOf; +type IOUAction = ValueOf; -export type {Country, IOUType}; +export type {Country, IOUAction, IOUType, RateAndUnit}; export default CONST; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index cda74da86a54..819680db0e8a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -126,7 +126,7 @@ const ONYXKEYS = { /** The NVP with the last payment method used per policy */ NVP_LAST_PAYMENT_METHOD: 'nvp_private_lastPaymentMethod', - /** This NVP holds to most recent waypoints that a person has used when creating a distance request */ + /** This NVP holds to most recent waypoints that a person has used when creating a distance expense */ NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints', /** This NVP will be `true` if the user has ever dismissed the engagement modal on either OldDot or NewDot. If it becomes true it should stay true forever. */ @@ -157,7 +157,7 @@ const ONYXKEYS = { FREQUENTLY_USED_EMOJIS: 'nvp_expensify_frequentlyUsedEmojis', /** The NVP with the last distance rate used per policy */ - NVP_LAST_SELECTED_DISTANCE_RATES: 'lastSelectedDistanceRates', + NVP_LAST_SELECTED_DISTANCE_RATES: 'nvp_expensify_lastSelectedDistanceRates', /** The NVP with the last action taken (for the Quick Action Button) */ NVP_QUICK_ACTION_GLOBAL_CREATE: 'nvp_quickActionGlobalCreate', @@ -312,15 +312,19 @@ const ONYXKEYS = { COLLECTION: { DOWNLOAD: 'download_', POLICY: 'policy_', - POLICY_MEMBERS: 'policyMembers_', POLICY_DRAFTS: 'policyDrafts_', - POLICY_MEMBERS_DRAFTS: 'policyMembersDrafts_', POLICY_JOIN_MEMBER: 'policyJoinMember_', POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', POLICY_RECENTLY_USED_TAGS: 'nvp_recentlyUsedTags_', + // Whether the policy's connection data was attempted to be fetched in + // the current user session. As this state only exists client-side, it + // should not be included as part of the policy object. The policy + // object should mirror the data as it's stored in the database. + POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED: 'policyHasConnectionsDataBeenFetched_', OLD_POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', + POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', REPORT: 'report_', @@ -340,20 +344,17 @@ const ONYXKEYS = { TRANSACTION: 'transactions_', TRANSACTION_VIOLATIONS: 'transactionViolations_', TRANSACTION_DRAFT: 'transactionsDraft_', - - // Holds temporary transactions used during the creation and edit flow + SKIP_CONFIRMATION: 'skipConfirmation_', TRANSACTION_BACKUP: 'transactionsBackup_', SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_', PRIVATE_NOTES_DRAFT: 'privateNotesDraft_', NEXT_STEP: 'reportNextStep_', - // Manual request tab selector + // Manual expense tab selector SELECTED_TAB: 'selectedTab_', /** This is deprecated, but needed for a migration, so we still need to include it here so that it will be initialized in Onyx.init */ DEPRECATED_POLICY_MEMBER_LIST: 'policyMemberList_', - - POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_', }, /** List of Form ids */ @@ -526,10 +527,9 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; - [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; + [ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean; + [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; @@ -545,6 +545,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; + [ONYXKEYS.COLLECTION.SKIP_CONFIRMATION]: boolean; [ONYXKEYS.COLLECTION.TRANSACTION_BACKUP]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; [ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT]: OnyxTypes.Transaction; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 8e4c84f24965..ceb4c217cb6e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1,5 +1,6 @@ import type {ValueOf} from 'type-fest'; import type CONST from './CONST'; +import type {IOUAction, IOUType} from './CONST'; import type {IOURequestType} from './libs/actions/IOU'; import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual'; @@ -22,13 +23,18 @@ const ROUTES = { ALL_SETTINGS: 'all-settings', + SEARCH: { + route: '/search/:query', + getRoute: (query: string) => `search/${query}` as const, + }, + // This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated CONCIERGE: 'concierge', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` as const, }, - SEARCH: 'search', + CHAT_FINDER: 'chat-finder', DETAILS: { route: 'details', getRoute: (login: string) => `details?login=${encodeURIComponent(login)}` as const, @@ -205,7 +211,7 @@ const ROUTES = { EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field/:tagIndex?', getRoute: (threadReportID: string, field: ValueOf, tagIndex?: number) => - `r/${threadReportID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, + `r/${threadReportID}/edit/${field as string}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', @@ -274,7 +280,7 @@ const ROUTES = { EDIT_SPLIT_BILL: { route: `r/:reportID/split/:reportActionID/edit/:field/:tagIndex?`, getRoute: (reportID: string, reportActionID: string, field: ValueOf, tagIndex?: number) => - `r/${reportID}/split/${reportActionID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, + `r/${reportID}/split/${reportActionID}/edit/${field as string}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, }, TASK_TITLE: { route: 'r/:reportID/title', @@ -301,127 +307,119 @@ const ROUTES = { getRoute: (reportID: string) => `r/${reportID}/members` as const, }, ROOM_INVITE: { - route: 'r/:reportID/invite', - getRoute: (reportID: string) => `r/${reportID}/invite` as const, - }, - MONEY_REQUEST_PARTICIPANTS: { - route: ':iouType/new/participants/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` as const, + route: 'r/:reportID/invite/:role?', + getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role}` as const, }, MONEY_REQUEST_HOLD_REASON: { - route: ':iouType/edit/reason/:transactionID?', - getRoute: (iouType: string, transactionID: string, reportID: string, backTo: string) => `${iouType}/edit/reason/${transactionID}?backTo=${backTo}&reportID=${reportID}` as const, + route: ':type/edit/reason/:transactionID?', + getRoute: (type: ValueOf, transactionID: string, reportID: string, backTo: string) => + `${type}/edit/reason/${transactionID}?backTo=${backTo}&reportID=${reportID}` as const, }, MONEY_REQUEST_MERCHANT: { route: ':iouType/new/merchant/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` as const, + getRoute: (iouType: IOUType, reportID = '') => `${iouType}/new/merchant/${reportID}` as const, }, MONEY_REQUEST_RECEIPT: { route: ':iouType/new/receipt/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` as const, + getRoute: (iouType: IOUType, reportID = '') => `${iouType}/new/receipt/${reportID}` as const, }, MONEY_REQUEST_CREATE: { route: ':action/:iouType/start/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `${action}/${iouType}/start/${transactionID}/${reportID}` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `${action as string}/${iouType as string}/start/${transactionID}/${reportID}` as const, }, MONEY_REQUEST_STEP_CONFIRMATION: { route: ':action/:iouType/confirmation/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `${action}/${iouType}/confirmation/${transactionID}/${reportID}` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => + `${action as string}/${iouType as string}/confirmation/${transactionID}/${reportID}` as const, }, MONEY_REQUEST_STEP_AMOUNT: { route: ':action/:iouType/amount/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/amount/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/amount/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAX_RATE: { route: ':action/:iouType/taxRate/:transactionID/:reportID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/taxRate/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/taxRate/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAX_AMOUNT: { route: ':action/:iouType/taxAmount/:transactionID/:reportID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/taxAmount/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/taxAmount/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_CATEGORY: { route: ':action/:iouType/category/:transactionID/:reportID/:reportActionID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => - getUrlWithBackToParam(`${action}/${iouType}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getUrlWithBackToParam(`${action as string}/${iouType as string}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_CURRENCY: { route: ':action/:iouType/currency/:transactionID/:reportID/:pageIndex?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, pageIndex = '', currency = '', backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/currency/${transactionID}/${reportID}/${pageIndex}?currency=${currency}`, backTo), }, MONEY_REQUEST_STEP_DATE: { route: ':action/:iouType/date/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/date/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/date/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_DESCRIPTION: { route: ':action/:iouType/description/:transactionID/:reportID/:reportActionID?', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => - getUrlWithBackToParam(`${action}/${iouType}/description/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getUrlWithBackToParam(`${action as string}/${iouType as string}/description/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_DISTANCE: { route: ':action/:iouType/distance/:transactionID/:reportID', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/distance/${transactionID}/${reportID}`, backTo), + }, + MONEY_REQUEST_STEP_DISTANCE_RATE: { + route: ':action/:iouType/distanceRate/:transactionID/:reportID', getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/distance/${transactionID}/${reportID}`, backTo), + getUrlWithBackToParam(`${action}/${iouType}/distanceRate/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_MERCHANT: { route: ':action/:iouType/merchant/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/merchant/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/merchant/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_PARTICIPANTS: { - route: 'create/:iouType/participants/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/participants/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/participants/:transactionID/:reportID', + getRoute: (iouType: IOUType, transactionID: string, reportID: string, backTo = '', action: IOUAction = 'create') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/participants/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_SCAN: { route: ':action/:iouType/scan/:transactionID/:reportID', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/scan/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAG: { route: ':action/:iouType/tag/:orderWeight/:transactionID/:reportID/:reportActionID?', - getRoute: ( - action: ValueOf, - iouType: ValueOf, - orderWeight: number, - transactionID: string, - reportID: string, - backTo = '', - reportActionID?: string, - ) => getUrlWithBackToParam(`${action}/${iouType}/tag/${orderWeight}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, orderWeight: number, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getUrlWithBackToParam(`${action as string}/${iouType as string}/tag/${orderWeight}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID?: string, pageIndex = '', backTo = '') => - getUrlWithBackToParam(`${action}/${iouType}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID?: string, pageIndex = '', backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), }, // This URL is used as a redirect to one of the create tabs below. This is so that we can message users with a link // straight to those flows without needing to have optimistic transaction and report IDs. MONEY_REQUEST_START: { route: 'start/:iouType/:iouRequestType', - getRoute: (iouType: ValueOf, iouRequestType: IOURequestType) => `start/${iouType}/${iouRequestType}` as const, + getRoute: (iouType: IOUType, iouRequestType: IOURequestType) => `start/${iouType as string}/${iouRequestType}` as const, }, MONEY_REQUEST_CREATE_TAB_DISTANCE: { route: ':action/:iouType/start/:transactionID/:reportID/distance', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `create/${iouType}/start/${transactionID}/${reportID}/distance` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/distance` as const, }, MONEY_REQUEST_CREATE_TAB_MANUAL: { route: ':action/:iouType/start/:transactionID/:reportID/manual', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `${action}/${iouType}/start/${transactionID}/${reportID}/manual` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => + `${action as string}/${iouType as string}/start/${transactionID}/${reportID}/manual` as const, }, MONEY_REQUEST_CREATE_TAB_SCAN: { route: ':action/:iouType/start/:transactionID/:reportID/scan', - getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string) => - `create/${iouType}/start/${transactionID}/${reportID}/scan` as const, + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) => `create/${iouType as string}/start/${transactionID}/${reportID}/scan` as const, }, MONEY_REQUEST_STATE_SELECTOR: { @@ -563,7 +561,7 @@ const ROUTES = { route: 'settings/workspaces/:policyID/members', getRoute: (policyID: string) => `settings/workspaces/${policyID}/members` as const, }, - WORKSPACE_ACCOUNTING: { + POLICY_ACCOUNTING: { route: 'settings/workspaces/:policyID/accounting', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting` as const, }, @@ -654,7 +652,7 @@ const ROUTES = { WORKSPACE_OWNER_CHANGE_CHECK: { route: 'settings/workspaces/:policyID/change-owner/:accountID/:error', getRoute: (policyID: string, accountID: number, error: ValueOf) => - `settings/workspaces/${policyID}/change-owner/${accountID}/${error}` as const, + `settings/workspaces/${policyID}/change-owner/${accountID}/${error as string}` as const, }, WORKSPACE_TAX_CREATE: { route: 'settings/workspaces/:policyID/taxes/new', @@ -707,27 +705,27 @@ const ROUTES = { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/accounts', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/accounts` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/classes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/classes` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CUSTOMERS: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/customers', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/customers` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_LOCATIONS: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/locations', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/locations` as const, }, - WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: { + POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES: { route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const, }, @@ -742,7 +740,7 @@ const HYBRID_APP_ROUTES = { MONEY_REQUEST_CREATE: '/request/new/scan', } as const; -export {getUrlWithBackToParam, HYBRID_APP_ROUTES}; +export {HYBRID_APP_ROUTES, getUrlWithBackToParam}; export default ROUTES; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -762,4 +760,4 @@ type RouteIsPlainString = AssertTypesNotEqual = { +const imagePickerOptions = { includeBase64: false, saveToPhotos: false, selectionLimit: 1, includeExtra: false, - assetRepresentationMode: 'current', }; /** @@ -237,7 +236,10 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const validateAndCompleteAttachmentSelection = useCallback( (fileData: FileResponse) => { - if (fileData.width === -1 || fileData.height === -1) { + // Check if the file dimensions indicate corruption + // The width/height for a corrupted file is -1 on android native and 0 on ios native + // We must check only numeric values because the width/height can be undefined for non-image files + if ((typeof fileData.width === 'number' && fileData.width <= 0) || (typeof fileData.height === 'number' && fileData.height <= 0)) { showImageCorruptionAlert(); return Promise.resolve(); } @@ -283,16 +285,18 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s }; /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ if (fileDataName && Str.isImage(fileDataName)) { - ImageSize.getSize(fileDataUri).then(({width, height}) => { - fileDataObject.width = width; - fileDataObject.height = height; - validateAndCompleteAttachmentSelection(fileDataObject); - }); + ImageSize.getSize(fileDataUri) + .then(({width, height}) => { + fileDataObject.width = width; + fileDataObject.height = height; + validateAndCompleteAttachmentSelection(fileDataObject); + }) + .catch(() => showImageCorruptionAlert()); } else { return validateAndCompleteAttachmentSelection(fileDataObject); } }, - [validateAndCompleteAttachmentSelection], + [validateAndCompleteAttachmentSelection, showImageCorruptionAlert], ); /** diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 358f5333bfba..bf48894beaab 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -90,7 +90,7 @@ function Avatar({ if (isWorkspace) { iconColors = StyleUtils.getDefaultWorkspaceAvatarColor(name); } else if (useFallBackAvatar) { - iconColors = StyleUtils.getBackgroundColorAndFill(theme.border, theme.icon); + iconColors = StyleUtils.getBackgroundColorAndFill(theme.buttonHoveredBG, theme.icon); } else { iconColors = null; } diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index f6afb4dae2d6..c7a4ece0de97 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -65,7 +65,6 @@ function AvatarWithDisplayName({ const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails) as PersonalDetails[], false); const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report); - const isExpenseRequest = ReportUtils.isExpenseRequest(report); const avatarBorderColor = isAnonymous ? theme.highlightBG : theme.componentBG; const actorAccountID = useRef(null); @@ -128,7 +127,7 @@ function AvatarWithDisplayName({ /> )} - + ; }; diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx new file mode 100644 index 000000000000..4d482cb92ead --- /dev/null +++ b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx @@ -0,0 +1,68 @@ +import React, {useState} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import {WebView} from 'react-native-webview'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import Button from '@components/Button'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Modal from '@components/Modal'; +import useLocalize from '@hooks/useLocalize'; +import {getQuickBooksOnlineSetupLink} from '@libs/actions/connections/QuickBooksOnline'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Session} from '@src/types/onyx'; +import type {ConnectToQuickbooksOnlineButtonProps} from './types'; + +type ConnectToQuickbooksOnlineButtonOnyxProps = { + /** Session info for the currently logged in user. */ + session: OnyxEntry; +}; + +const renderLoading = () => ; + +function ConnectToQuickbooksOnlineButton({policyID, session}: ConnectToQuickbooksOnlineButtonProps & ConnectToQuickbooksOnlineButtonOnyxProps) { + const [isModalOpen, setIsModalOpen] = useState(false); + const {translate} = useLocalize(); + + return ( + <> +