Skip to content

Commit ef55d3a

Browse files
committed
Merge branch 'main' into xero-merge-freeze
2 parents 990ab4f + ea41f1d commit ef55d3a

File tree

14 files changed

+88
-56
lines changed

14 files changed

+88
-56
lines changed

android/app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ android {
107107
minSdkVersion rootProject.ext.minSdkVersion
108108
targetSdkVersion rootProject.ext.targetSdkVersion
109109
multiDexEnabled rootProject.ext.multiDexEnabled
110-
versionCode 1001047703
111-
versionName "1.4.77-3"
110+
versionCode 1001047705
111+
versionName "1.4.77-5"
112112
// Supported language variants must be declared here to avoid from being removed during the compilation.
113113
// This also helps us to not include unnecessary language variants in the APK.
114114
resConfigs "en", "es"

ios/NewExpensify/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
</dict>
4141
</array>
4242
<key>CFBundleVersion</key>
43-
<string>1.4.77.3</string>
43+
<string>1.4.77.5</string>
4444
<key>FullStory</key>
4545
<dict>
4646
<key>OrgId</key>

ios/NewExpensifyTests/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>
22-
<string>1.4.77.3</string>
22+
<string>1.4.77.5</string>
2323
</dict>
2424
</plist>

ios/NotificationServiceExtension/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<key>CFBundleShortVersionString</key>
1414
<string>1.4.77</string>
1515
<key>CFBundleVersion</key>
16-
<string>1.4.77.3</string>
16+
<string>1.4.77.5</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionPointIdentifier</key>

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "new.expensify",
3-
"version": "1.4.77-3",
3+
"version": "1.4.77-5",
44
"author": "Expensify, Inc.",
55
"homepage": "https://new.expensify.com",
66
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",

src/CONST.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,9 @@ const CONST = {
15961596
ACCOUNTANT: 'accountant',
15971597
},
15981598
},
1599+
ACCESS_VARIANTS: {
1600+
CREATE: 'create',
1601+
},
15991602
},
16001603

16011604
GROWL: {

src/components/ConnectionLayout.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import useLocalize from '@hooks/useLocalize';
66
import useThemeStyles from '@hooks/useThemeStyles';
77
import Navigation from '@libs/Navigation/Navigation';
88
import * as PolicyUtils from '@libs/PolicyUtils';
9-
import type {PolicyAccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
9+
import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
1010
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
1111
import type {TranslationPaths} from '@src/languages/types';
1212
import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy';
@@ -35,7 +35,7 @@ type ConnectionLayoutProps = {
3535
policyID: string;
3636

3737
/** Defines which types of access should be verified */
38-
accessVariants?: PolicyAccessVariant[];
38+
accessVariants?: AccessVariant[];
3939

4040
/** The current feature name that the user tries to get access to */
4141
featureName?: PolicyFeatureName;

src/components/MapView/MapView.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {useFocusEffect, useNavigation} from '@react-navigation/native';
22
import type {MapState} from '@rnmapbox/maps';
33
import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps';
4-
import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
4+
import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
55
import {View} from 'react-native';
66
import {withOnyx} from 'react-native-onyx';
77
import useThemeStyles from '@hooks/useThemeStyles';
@@ -30,7 +30,8 @@ const MapView = forwardRef<MapViewHandle, ComponentProps>(
3030

3131
const cameraRef = useRef<Mapbox.Camera>(null);
3232
const [isIdle, setIsIdle] = useState(false);
33-
const [currentPosition, setCurrentPosition] = useState(cachedUserLocation);
33+
const initialLocation = useMemo(() => initialState && {longitude: initialState.location[0], latitude: initialState.location[1]}, [initialState]);
34+
const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation);
3435
const [userInteractedWithMap, setUserInteractedWithMap] = useState(false);
3536
const shouldInitializeCurrentPosition = useRef(true);
3637

@@ -42,13 +43,13 @@ const MapView = forwardRef<MapViewHandle, ComponentProps>(
4243

4344
const setCurrentPositionToInitialState: GeolocationErrorCallback = useCallback(
4445
(error) => {
45-
if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialState) {
46+
if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialLocation) {
4647
return;
4748
}
4849
UserLocation.clearUserLocation();
49-
setCurrentPosition({longitude: initialState.location[0], latitude: initialState.location[1]});
50+
setCurrentPosition(initialLocation);
5051
},
51-
[initialState],
52+
[initialLocation],
5253
);
5354

5455
useFocusEffect(

src/components/MapView/MapView.website.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import {useFocusEffect} from '@react-navigation/native';
66
import mapboxgl from 'mapbox-gl';
77
import 'mapbox-gl/dist/mapbox-gl.css';
8-
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
8+
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
99
import type {MapRef} from 'react-map-gl';
1010
import Map, {Marker} from 'react-map-gl';
1111
import {View} from 'react-native';
@@ -52,7 +52,8 @@ const MapView = forwardRef<MapViewHandle, ComponentProps>(
5252
const StyleUtils = useStyleUtils();
5353

5454
const [mapRef, setMapRef] = useState<MapRef | null>(null);
55-
const [currentPosition, setCurrentPosition] = useState(cachedUserLocation);
55+
const initialLocation = useMemo(() => ({longitude: initialState.location[0], latitude: initialState.location[1]}), [initialState]);
56+
const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation);
5657
const [userInteractedWithMap, setUserInteractedWithMap] = useState(false);
5758
const [shouldResetBoundaries, setShouldResetBoundaries] = useState<boolean>(false);
5859
const setRef = useCallback((newRef: MapRef | null) => setMapRef(newRef), []);
@@ -66,13 +67,13 @@ const MapView = forwardRef<MapViewHandle, ComponentProps>(
6667

6768
const setCurrentPositionToInitialState: GeolocationErrorCallback = useCallback(
6869
(error) => {
69-
if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialState) {
70+
if (error?.code !== GeolocationErrorCode.PERMISSION_DENIED || !initialLocation) {
7071
return;
7172
}
7273
UserLocation.clearUserLocation();
73-
setCurrentPosition({longitude: initialState.location[0], latitude: initialState.location[1]});
74+
setCurrentPosition(initialLocation);
7475
},
75-
[initialState],
76+
[initialLocation],
7677
);
7778

7879
useFocusEffect(

src/components/SelectionScreen.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {isEmpty} from 'lodash';
22
import React from 'react';
33
import useLocalize from '@hooks/useLocalize';
44
import * as PolicyUtils from '@libs/PolicyUtils';
5-
import type {PolicyAccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
5+
import type {AccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
66
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
77
import type {TranslationPaths} from '@src/languages/types';
88
import type {ConnectionName, PolicyFeatureName} from '@src/types/onyx/Policy';
@@ -47,7 +47,7 @@ type SelectionScreenProps = {
4747
policyID: string;
4848

4949
/** Defines which types of access should be verified */
50-
accessVariants?: PolicyAccessVariant[];
50+
accessVariants?: AccessVariant[];
5151

5252
/** The current feature name that the user tries to get access to */
5353
featureName?: PolicyFeatureName;

src/pages/iou/request/IOURequestStartPage.tsx

+18-17
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
33
import {View} from 'react-native';
44
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
55
import {withOnyx} from 'react-native-onyx';
6-
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
76
import DragAndDropProvider from '@components/DragAndDrop/Provider';
87
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
98
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -13,13 +12,12 @@ import useLocalize from '@hooks/useLocalize';
1312
import usePermissions from '@hooks/usePermissions';
1413
import useThemeStyles from '@hooks/useThemeStyles';
1514
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
16-
import * as IOUUtils from '@libs/IOUUtils';
1715
import * as KeyDownPressListener from '@libs/KeyboardShortcut/KeyDownPressListener';
1816
import Navigation from '@libs/Navigation/Navigation';
1917
import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator';
20-
import * as PolicyUtils from '@libs/PolicyUtils';
2118
import * as ReportUtils from '@libs/ReportUtils';
2219
import * as TransactionUtils from '@libs/TransactionUtils';
20+
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
2321
import * as IOU from '@userActions/IOU';
2422
import type {IOURequestType} from '@userActions/IOU';
2523
import CONST from '@src/CONST';
@@ -105,9 +103,6 @@ function IOURequestStartPage({
105103
const isExpenseReport = ReportUtils.isExpenseReport(report);
106104
const shouldDisplayDistanceRequest = (!!canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate) && iouType !== CONST.IOU.TYPE.SPLIT;
107105

108-
// Allow the user to submit the expense if we are submitting the expense in global menu or the report can create the exoense
109-
const isAllowedToCreateRequest = isEmptyObject(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType) || PolicyUtils.canSendInvoice(allPolicies);
110-
111106
const navigateBack = () => {
112107
Navigation.closeRHPFlow();
113108
};
@@ -126,15 +121,21 @@ function IOURequestStartPage({
126121
}
127122

128123
return (
129-
<ScreenWrapper
130-
includeSafeAreaPaddingBottom={false}
131-
shouldEnableKeyboardAvoidingView={false}
132-
shouldEnableMinHeight={DeviceCapabilities.canUseTouchScreen()}
133-
headerGapStyles={isDraggingOver ? [styles.receiptDropHeaderGap] : []}
134-
testID={IOURequestStartPage.displayName}
124+
<AccessOrNotFoundWrapper
125+
reportID={reportID}
126+
iouType={iouType}
127+
policyID={policy?.id}
128+
accessVariants={[CONST.IOU.ACCESS_VARIANTS.CREATE]}
129+
allPolicies={allPolicies}
135130
>
136-
{({safeAreaPaddingBottomStyle}) => (
137-
<FullPageNotFoundView shouldShow={!IOUUtils.isValidMoneyRequestType(iouType) || !isAllowedToCreateRequest}>
131+
<ScreenWrapper
132+
includeSafeAreaPaddingBottom={false}
133+
shouldEnableKeyboardAvoidingView={false}
134+
shouldEnableMinHeight={DeviceCapabilities.canUseTouchScreen()}
135+
headerGapStyles={isDraggingOver ? [styles.receiptDropHeaderGap] : []}
136+
testID={IOURequestStartPage.displayName}
137+
>
138+
{({safeAreaPaddingBottomStyle}) => (
138139
<DragAndDropProvider
139140
setIsDraggingOver={setIsDraggingOver}
140141
isDisabled={selectedTab !== CONST.TAB_REQUEST.SCAN}
@@ -159,9 +160,9 @@ function IOURequestStartPage({
159160
)}
160161
</View>
161162
</DragAndDropProvider>
162-
</FullPageNotFoundView>
163-
)}
164-
</ScreenWrapper>
163+
)}
164+
</ScreenWrapper>
165+
</AccessOrNotFoundWrapper>
165166
);
166167
}
167168

src/pages/workspace/AccessOrNotFoundWrapper.tsx

+42-16
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
/* eslint-disable rulesdir/no-negated-variables */
22
import React, {useEffect} from 'react';
3-
import type {OnyxEntry} from 'react-native-onyx';
3+
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
44
import {withOnyx} from 'react-native-onyx';
55
import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPageNotFoundView';
66
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
77
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
8+
import * as IOUUtils from '@libs/IOUUtils';
89
import Navigation from '@libs/Navigation/Navigation';
910
import * as PolicyUtils from '@libs/PolicyUtils';
11+
import * as ReportUtils from '@libs/ReportUtils';
1012
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
1113
import * as Policy from '@userActions/Policy/Policy';
14+
import type {IOUType} from '@src/CONST';
1215
import CONST from '@src/CONST';
1316
import ONYXKEYS from '@src/ONYXKEYS';
1417
import ROUTES from '@src/ROUTES';
@@ -17,13 +20,22 @@ import type {PolicyFeatureName} from '@src/types/onyx/Policy';
1720
import callOrReturn from '@src/types/utils/callOrReturn';
1821
import {isEmptyObject} from '@src/types/utils/EmptyObject';
1922

20-
const POLICY_ACCESS_VARIANTS = {
23+
const ACCESS_VARIANTS = {
2124
[CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry<OnyxTypes.Policy>) => PolicyUtils.isPaidGroupPolicy(policy) && !!policy?.isPolicyExpenseChatEnabled,
2225
[CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry<OnyxTypes.Policy>) => PolicyUtils.isPolicyAdmin(policy),
23-
} as const satisfies Record<string, (policy: OnyxTypes.Policy) => boolean>;
24-
25-
type PolicyAccessVariant = keyof typeof POLICY_ACCESS_VARIANTS;
26+
[CONST.IOU.ACCESS_VARIANTS.CREATE]: (policy: OnyxEntry<OnyxTypes.Policy>, report: OnyxEntry<OnyxTypes.Report>, allPolicies: OnyxCollection<OnyxTypes.Policy>, iouType?: IOUType) =>
27+
!!iouType &&
28+
IOUUtils.isValidMoneyRequestType(iouType) &&
29+
// Allow the user to submit the expense if we are submitting the expense in global menu or the report can create the expense
30+
(isEmptyObject(report?.reportID) || ReportUtils.canCreateRequest(report, policy, iouType)) &&
31+
(iouType !== CONST.IOU.TYPE.INVOICE || PolicyUtils.canSendInvoice(allPolicies)),
32+
} as const satisfies Record<string, (policy: OnyxTypes.Policy, report: OnyxTypes.Report, allPolicies: OnyxCollection<OnyxTypes.Policy>, iouType?: IOUType) => boolean>;
33+
34+
type AccessVariant = keyof typeof ACCESS_VARIANTS;
2635
type AccessOrNotFoundWrapperOnyxProps = {
36+
/** The report that holds the transaction */
37+
report: OnyxEntry<OnyxTypes.Report>;
38+
2739
/** The report currently being looked at */
2840
policy: OnyxEntry<OnyxTypes.Policy>;
2941

@@ -35,11 +47,14 @@ type AccessOrNotFoundWrapperProps = AccessOrNotFoundWrapperOnyxProps & {
3547
/** The children to render */
3648
children: ((props: AccessOrNotFoundWrapperOnyxProps) => React.ReactNode) | React.ReactNode;
3749

50+
/** The id of the report that holds the transaction */
51+
reportID?: string;
52+
3853
/** The report currently being looked at */
39-
policyID: string;
54+
policyID?: string;
4055

4156
/** Defines which types of access should be verified */
42-
accessVariants?: PolicyAccessVariant[];
57+
accessVariants?: AccessVariant[];
4358

4459
/** The current feature name that the user tries to get access to */
4560
featureName?: PolicyFeatureName;
@@ -49,6 +64,12 @@ type AccessOrNotFoundWrapperProps = AccessOrNotFoundWrapperOnyxProps & {
4964

5065
/** Whether or not to block user from accessing the page */
5166
shouldBeBlocked?: boolean;
67+
68+
/** The type of the transaction */
69+
iouType?: IOUType;
70+
71+
/** The list of all policies */
72+
allPolicies?: OnyxCollection<OnyxTypes.Policy>;
5273
} & Pick<FullPageNotFoundViewProps, 'subtitleKey' | 'onLinkPress'>;
5374

5475
type PageNotFoundFallbackProps = Pick<AccessOrNotFoundWrapperProps, 'policyID' | 'fullPageNotFoundViewProps'> & {shouldShowFullScreenFallback: boolean};
@@ -64,17 +85,19 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN
6485
/>
6586
) : (
6687
<NotFoundPage
67-
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(policyID))}
88+
onBackButtonPress={() => Navigation.goBack(policyID ? ROUTES.WORKSPACE_PROFILE.getRoute(policyID) : ROUTES.HOME)}
6889
// eslint-disable-next-line react/jsx-props-no-spreading
6990
{...fullPageNotFoundViewProps}
7091
/>
7192
);
7293
}
7394

7495
function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps, shouldBeBlocked, ...props}: AccessOrNotFoundWrapperProps) {
75-
const {policy, policyID, featureName, isLoadingReportData} = props;
96+
const {policy, policyID, report, iouType, allPolicies, featureName, isLoadingReportData} = props;
7697

7798
const isPolicyIDInRoute = !!policyID?.length;
99+
const isMoneyRequest = !!iouType && IOUUtils.isValidMoneyRequestType(iouType);
100+
const isFromGlobalCreate = isEmptyObject(report?.reportID);
78101

79102
useEffect(() => {
80103
if (!isPolicyIDInRoute || !isEmptyObject(policy)) {
@@ -86,17 +109,17 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps
86109
// eslint-disable-next-line react-hooks/exhaustive-deps
87110
}, [isPolicyIDInRoute, policyID]);
88111

89-
const shouldShowFullScreenLoadingIndicator = isLoadingReportData !== false && (!Object.entries(policy ?? {}).length || !policy?.id);
112+
const shouldShowFullScreenLoadingIndicator = !isMoneyRequest && isLoadingReportData !== false && (!Object.entries(policy ?? {}).length || !policy?.id);
90113

91114
const isFeatureEnabled = featureName ? PolicyUtils.isPolicyFeatureEnabled(policy, featureName) : true;
92115

93116
const isPageAccessible = accessVariants.reduce((acc, variant) => {
94-
const accessFunction = POLICY_ACCESS_VARIANTS[variant];
95-
return acc && accessFunction(policy);
117+
const accessFunction = ACCESS_VARIANTS[variant];
118+
return acc && accessFunction(policy, report, allPolicies ?? null, iouType);
96119
}, true);
97120

98-
const shouldShowNotFoundPage =
99-
isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors)) || !policy?.id || !isPageAccessible || !isFeatureEnabled || shouldBeBlocked;
121+
const isPolicyNotAccessible = isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors)) || !policy?.id;
122+
const shouldShowNotFoundPage = (!isMoneyRequest && !isFromGlobalCreate && isPolicyNotAccessible) || !isPageAccessible || !isFeatureEnabled || shouldBeBlocked;
100123

101124
if (shouldShowFullScreenLoadingIndicator) {
102125
return <FullscreenLoadingIndicator />;
@@ -115,11 +138,14 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps
115138
return callOrReturn(props.children, props);
116139
}
117140

118-
export type {PolicyAccessVariant};
141+
export type {AccessVariant};
119142

120143
export default withOnyx<AccessOrNotFoundWrapperProps, AccessOrNotFoundWrapperOnyxProps>({
144+
report: {
145+
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
146+
},
121147
policy: {
122-
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`,
148+
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
123149
},
124150
isLoadingReportData: {
125151
key: ONYXKEYS.IS_LOADING_REPORT_DATA,

0 commit comments

Comments
 (0)