diff --git a/.github/workflows/testGithubActionsWorkflows.yml b/.github/workflows/testGithubActionsWorkflows.yml
index 58de3ba2d9f3..d052b343cf0c 100644
--- a/.github/workflows/testGithubActionsWorkflows.yml
+++ b/.github/workflows/testGithubActionsWorkflows.yml
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
workflow_call:
pull_request:
- types: [opened, reopened, edited, synchronize]
+ types: [opened, reopened, synchronize]
branches-ignore: [staging, production]
paths: ['.github/**']
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4dd8d1766953..e93150b48fca 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -96,8 +96,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001042002
- versionName "1.4.20-2"
+ versionCode 1001042101
+ versionName "1.4.21-1"
}
flavorDimensions "default"
diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md
index 65361ba1af9a..0856e2694340 100644
--- a/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md
+++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md
@@ -3,7 +3,7 @@ title: Certinia
description: Guide to connecting Expensify and Certinia FFA and PSA/SRP (formerly known as FinancialForce)
---
# Overview
-[Cetinia](https://use.expensify.com/financialforce) (Formerly known as FinancialForce)is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both.
+[Cetinia](https://use.expensify.com/financialforce) (formerly known as FinancialForce) is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both.
# Before connecting to Certinia
Install the Expensify bundle in Certinia using the relevant installer:
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 8d60003e1355..f267261a49c0 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.20
+ 1.4.21
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.20.2
+ 1.4.21.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 740a07ea24ac..f95a3f871d4c 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.20
+ 1.4.21
CFBundleSignature
????
CFBundleVersion
- 1.4.20.2
+ 1.4.21.1
diff --git a/package-lock.json b/package-lock.json
index e00ae28b8113..c8b4b2ba2082 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.20-2",
+ "version": "1.4.21-1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.20-2",
+ "version": "1.4.21-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 28dc12938d60..bd4b8ffacedf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.20-2",
+ "version": "1.4.21-1",
"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.",
diff --git a/src/CONST.ts b/src/CONST.ts
index abba27b0c33b..cec7cbc0b8a5 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -719,6 +719,7 @@ const CONST = {
TRIE_INITIALIZATION: 'trie_initialization',
COMMENT_LENGTH_DEBOUNCE_TIME: 500,
SEARCH_OPTION_LIST_DEBOUNCE_TIME: 300,
+ RESIZE_DEBOUNCE_TIME: 100,
},
PRIORITY_MODE: {
GSD: 'gsd',
@@ -853,7 +854,7 @@ const CONST = {
// It's copied here so that the same regex pattern can be used in form validations to be consistent with the server.
VALIDATE_FOR_HTML_TAG_REGEX: /<([^>\s]+)(?:[^>]*?)>/g,
- VALIDATE_FOR_LEADINGSPACES_HTML_TAG_REGEX: /<([\s]+[\s\w~!@#$%^&*(){}[\];':"`|?.,/\\+\-=<]+.*[\s]*)>/g,
+ VALIDATE_FOR_LEADINGSPACES_HTML_TAG_REGEX: /<([\s]+.+[\s]*)>/g,
WHITELISTED_TAGS: [/<>/, /< >/, /<->/, /<-->/, /
/, /
/],
@@ -979,6 +980,7 @@ const CONST = {
CHAT_FOOTER_SECONDARY_ROW_HEIGHT: 15,
CHAT_FOOTER_SECONDARY_ROW_PADDING: 5,
CHAT_FOOTER_MIN_HEIGHT: 65,
+ CHAT_FOOTER_HORIZONTAL_PADDING: 40,
CHAT_SKELETON_VIEW: {
AVERAGE_ROW_HEIGHT: 80,
HEIGHT_FOR_ROW_COUNT: {
@@ -1174,6 +1176,7 @@ const CONST = {
EXPENSIFY: 'Expensify',
VBBA: 'ACH',
},
+ DEFAULT_AMOUNT: 0,
TYPE: {
SEND: 'send',
SPLIT: 'split',
@@ -2942,6 +2945,7 @@ const CONST = {
PARENT_CHILD_SEPARATOR: ': ',
CATEGORY_LIST_THRESHOLD: 8,
TAG_LIST_THRESHOLD: 8,
+ TAX_RATES_LIST_THRESHOLD: 8,
COLON: ':',
MAPBOX: {
PADDING: 50,
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 53cd37e71f67..dff3a0aa441b 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -250,6 +250,7 @@ const ONYXKEYS = {
POLICY_CATEGORIES: 'policyCategories_',
POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_',
POLICY_TAGS: 'policyTags_',
+ POLICY_TAX_RATE: 'policyTaxRates_',
POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_',
POLICY_REPORT_FIELDS: 'policyReportFields_',
POLICY_RECENTLY_USED_REPORT_FIELDS: 'policyRecentlyUsedReportFields_',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index db17378684d6..49067d1c7b8f 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -322,6 +322,16 @@ const ROUTES = {
getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`create/${iouType}/amount/${transactionID}/${reportID}/`, backTo),
},
+ MONEY_REQUEST_STEP_TAX_RATE: {
+ route: 'create/:iouType/taxRate/:transactionID/:reportID?',
+ getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo: string) =>
+ getUrlWithBackToParam(`create/${iouType}/taxRate/${transactionID}/${reportID}`, backTo),
+ },
+ MONEY_REQUEST_STEP_TAX_AMOUNT: {
+ route: 'create/:iouType/taxAmount/:transactionID/:reportID?',
+ getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo: string) =>
+ getUrlWithBackToParam(`create/${iouType}/taxAmount/${transactionID}/${reportID}`, backTo),
+ },
MONEY_REQUEST_STEP_CATEGORY: {
route: 'create/:iouType/category/:transactionID/:reportID/',
getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index c1d2059cd3b0..d86b7f893901 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -130,6 +130,8 @@ const SCREENS = {
STEP_SCAN: 'Money_Request_Step_Scan',
STEP_TAG: 'Money_Request_Step_Tag',
STEP_WAYPOINT: 'Money_Request_Step_Waypoint',
+ STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount',
+ STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate',
ROOT: 'Money_Request',
AMOUNT: 'Money_Request_Amount',
PARTICIPANTS: 'Money_Request_Participants',
diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js
index d9e4ef2c0f6e..31b04a3d954f 100644
--- a/src/components/AddressSearch/index.js
+++ b/src/components/AddressSearch/index.js
@@ -111,6 +111,9 @@ const propTypes = {
/** Information about the network */
network: networkPropTypes.isRequired,
+ /** Location bias for querying search results. */
+ locationBias: PropTypes.string,
+
...withLocalizePropTypes,
};
@@ -138,6 +141,7 @@ const defaultProps = {
maxInputLength: undefined,
predefinedPlaces: [],
resultTypes: 'address',
+ locationBias: undefined,
};
function AddressSearch({
@@ -162,6 +166,7 @@ function AddressSearch({
shouldSaveDraft,
translate,
value,
+ locationBias,
}) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -179,11 +184,11 @@ function AddressSearch({
language: preferredLocale,
types: resultTypes,
components: isLimitedToUSA ? 'country:us' : undefined,
+ ...(locationBias && {locationbias: locationBias}),
}),
- [preferredLocale, resultTypes, isLimitedToUSA],
+ [preferredLocale, resultTypes, isLimitedToUSA, locationBias],
);
const shouldShowCurrentLocationButton = canUseCurrentLocation && searchValue.trim().length === 0 && isFocused;
-
const saveLocationDetails = (autocompleteData, details) => {
const addressComponents = details.address_components;
if (!addressComponents) {
@@ -192,7 +197,7 @@ function AddressSearch({
// amount of data massaging needs to happen for what the parent expects to get from this function.
if (_.size(details)) {
onPress({
- address: lodashGet(details, 'description'),
+ address: autocompleteData.description || lodashGet(details, 'description', ''),
lat: lodashGet(details, 'geometry.location.lat', 0),
lng: lodashGet(details, 'geometry.location.lng', 0),
name: lodashGet(details, 'name'),
@@ -261,7 +266,7 @@ function AddressSearch({
lat: lodashGet(details, 'geometry.location.lat', 0),
lng: lodashGet(details, 'geometry.location.lng', 0),
- address: lodashGet(details, 'formatted_address', ''),
+ address: autocompleteData.description || lodashGet(details, 'formatted_address', ''),
};
// If the address is not in the US, use the full length state name since we're displaying the address's
diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx
index 8604d20130c7..2dae84106971 100644
--- a/src/components/ArchivedReportFooter.tsx
+++ b/src/components/ArchivedReportFooter.tsx
@@ -30,14 +30,14 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}}
const originalMessage = reportClosedAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED ? reportClosedAction.originalMessage : null;
const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT;
- let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[report?.ownerAccountID ?? 0]?.displayName);
+ let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[report?.ownerAccountID ?? 0]);
let oldDisplayName: string | undefined;
if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) {
const newAccountID = originalMessage?.newAccountID;
const oldAccountID = originalMessage?.oldAccountID;
- displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[newAccountID ?? 0]?.displayName);
- oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[oldAccountID ?? 0]?.displayName);
+ displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[newAccountID ?? 0]);
+ oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[oldAccountID ?? 0]);
}
const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT;
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index 863e59aa4474..51912c04eb31 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -358,6 +358,14 @@ function AttachmentModal(props) {
setIsModalOpen(true);
}, []);
+ useEffect(() => {
+ setSource(props.source);
+ }, [props.source]);
+
+ useEffect(() => {
+ setIsAuthTokenRequired(props.isAuthTokenRequired);
+ }, [props.isAuthTokenRequired]);
+
const sourceForAttachmentView = props.source || source;
const threeDotsMenuItems = useMemo(() => {
@@ -368,7 +376,7 @@ function AttachmentModal(props) {
const parentReportAction = props.parentReportActions[props.report.parentReportActionID];
const canEdit =
- ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT) &&
+ ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT, props.transaction) &&
!TransactionUtils.isDistanceRequest(props.transaction);
if (canEdit) {
menuItems.push({
@@ -396,7 +404,7 @@ function AttachmentModal(props) {
}
return menuItems;
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]);
+ }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file, source]);
// There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment.
// props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false.
diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx
index c2320f7c0202..b72662f989dc 100644
--- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx
+++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx
@@ -1,5 +1,5 @@
import {FlashList} from '@shopify/flash-list';
-import React, {ForwardedRef, forwardRef, ReactElement, useCallback, useEffect, useRef} from 'react';
+import React, {ForwardedRef, forwardRef, ReactElement, useCallback, useEffect, useMemo, useRef} from 'react';
import {View} from 'react-native';
// We take ScrollView from this package to properly handle the scrolling of AutoCompleteSuggestions in chats since one scroll is nested inside another
import {ScrollView} from 'react-native-gesture-handler';
@@ -8,6 +8,8 @@ import ColorSchemeWrapper from '@components/ColorSchemeWrapper';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import variables from '@styles/variables';
import CONST from '@src/CONST';
import viewForwardedRef from '@src/types/utils/viewForwardedRef';
import type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps} from './types';
@@ -39,6 +41,7 @@ function BaseAutoCompleteSuggestions(
}: AutoCompleteSuggestionsProps,
ref: ForwardedRef,
) {
+ const {windowWidth, isLargeScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const rowHeight = useSharedValue(0);
@@ -64,7 +67,13 @@ function BaseAutoCompleteSuggestions(
const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length;
const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value));
-
+ const estimatedListSize = useMemo(
+ () => ({
+ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length,
+ width: (isLargeScreenWidth ? windowWidth - variables.sideBarWidth : windowWidth) - CONST.CHAT_FOOTER_HORIZONTAL_PADDING,
+ }),
+ [isLargeScreenWidth, suggestions.length, windowWidth],
+ );
useEffect(() => {
rowHeight.value = withTiming(measureHeightOfSuggestionRows(suggestions.length, isSuggestionPickerLarge), {
duration: 100,
@@ -88,6 +97,7 @@ function BaseAutoCompleteSuggestions(
statusBarAnimation.value,
+ (current, previous) => {
+ // Do not run if either of the animated value is null
+ // or previous animated value is greater than or equal to the current one
+ if (previous === null || current === null || current <= previous) {
+ return;
+ }
+ const backgroundColor = interpolateColor(statusBarAnimation.value, [0, 1], [prevStatusBarBackgroundColor.current, statusBarBackgroundColor.current]);
+ runOnJS(updateStatusBarAppearance)({backgroundColor});
+ },
+ );
+
const listenerCount = useRef(0);
+
+ // Updates the status bar style and background color depending on the current route and theme
+ // This callback is triggered everytime the route changes or the theme changes
const updateStatusBarStyle = useCallback(
(listenerId?: number) => {
// Check if this function is either called through the current navigation listener or the general useEffect which listens for theme changes.
@@ -49,27 +70,40 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack
currentRoute = navigationRef.getCurrentRoute();
}
- let currentScreenBackgroundColor = theme.appBG;
let newStatusBarStyle = theme.statusBarStyle;
+ let currentScreenBackgroundColor = theme.appBG;
if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) {
- const screenTheme = theme.PAGE_THEMES[currentRoute.name];
- currentScreenBackgroundColor = screenTheme.backgroundColor;
- newStatusBarStyle = screenTheme.statusBarStyle;
+ const pageTheme = theme.PAGE_THEMES[currentRoute.name];
+
+ newStatusBarStyle = pageTheme.statusBarStyle;
+
+ const backgroundColorFromRoute =
+ currentRoute?.params && 'backgroundColor' in currentRoute.params && typeof currentRoute.params.backgroundColor === 'string' && currentRoute.params.backgroundColor;
+
+ // It's possible for backgroundColorFromRoute to be empty string, so we must use "||" to fallback to backgroundColorFallback.
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ currentScreenBackgroundColor = backgroundColorFromRoute || pageTheme.backgroundColor;
+ }
+
+ prevStatusBarBackgroundColor.current = statusBarBackgroundColor.current;
+ statusBarBackgroundColor.current = currentScreenBackgroundColor;
+
+ if (currentScreenBackgroundColor !== theme.appBG || prevStatusBarBackgroundColor.current !== theme.appBG) {
+ statusBarAnimation.value = 0;
+ statusBarAnimation.value = withDelay(300, withTiming(1));
}
// Don't update the status bar style if it's the same as the current one, to prevent flashing.
- if (newStatusBarStyle === statusBarStyle) {
- updateStatusBarAppearance({backgroundColor: currentScreenBackgroundColor});
- } else {
- updateStatusBarAppearance({backgroundColor: currentScreenBackgroundColor, statusBarStyle: newStatusBarStyle});
+ if (newStatusBarStyle !== statusBarStyle) {
+ updateStatusBarAppearance({statusBarStyle: newStatusBarStyle});
setStatusBarStyle(newStatusBarStyle);
}
},
- [statusBarStyle, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle],
+ [statusBarAnimation, statusBarStyle, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle],
);
// Add navigation state listeners to update the status bar every time the route changes
- // We have to pass a count as the listener id, because "react-navigation" somehow doesn't remove listeners properyl
+ // We have to pass a count as the listener id, because "react-navigation" somehow doesn't remove listeners properly
useEffect(() => {
if (isDisabled) {
return;
@@ -82,15 +116,6 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack
return () => navigationRef.removeListener('state', listener);
}, [isDisabled, theme.appBG, updateStatusBarStyle]);
- // Update the status bar style everytime the theme changes
- useEffect(() => {
- if (isDisabled) {
- return;
- }
-
- updateStatusBarStyle();
- }, [isDisabled, theme, updateStatusBarStyle]);
-
// Update the global background (on web) everytime the theme changes.
// The background of the html element needs to be updated, otherwise you will see a big contrast when resizing the window or when the keyboard is open on iOS web.
useEffect(() => {
diff --git a/src/components/DistanceMapView/distanceMapViewPropTypes.js b/src/components/DistanceMapView/distanceMapViewPropTypes.js
deleted file mode 100644
index f7a3bab1879e..000000000000
--- a/src/components/DistanceMapView/distanceMapViewPropTypes.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import PropTypes from 'prop-types';
-import sourcePropTypes from '@components/Image/sourcePropTypes';
-
-const propTypes = {
- // Public access token to be used to fetch map data from Mapbox.
- accessToken: PropTypes.string.isRequired,
-
- // Style applied to MapView component. Note some of the View Style props are not available on ViewMap
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
-
- // Link to the style JSON document.
- styleURL: PropTypes.string,
-
- // Whether map can tilt in the vertical direction.
- pitchEnabled: PropTypes.bool,
-
- // Padding to apply when the map is adjusted to fit waypoints and directions
- mapPadding: PropTypes.number,
-
- // Initial coordinate and zoom level
- initialState: PropTypes.shape({
- location: PropTypes.arrayOf(PropTypes.number).isRequired,
- zoom: PropTypes.number.isRequired,
- }),
-
- // Locations on which to put markers
- waypoints: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string,
- coordinate: PropTypes.arrayOf(PropTypes.number),
- markerComponent: sourcePropTypes,
- }),
- ),
-
- // List of coordinates which together forms a direction.
- directionCoordinates: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
-
- // Callback to call when the map is idle / ready
- onMapReady: PropTypes.func,
-
- // Optional additional styles to be applied to the overlay
- overlayStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
-};
-
-const defaultProps = {
- styleURL: undefined,
- pitchEnabled: false,
- mapPadding: 0,
- initialState: undefined,
- waypoints: undefined,
- directionCoordinates: undefined,
- onMapReady: () => {},
- overlayStyle: undefined,
-};
-
-export {propTypes, defaultProps};
diff --git a/src/components/DistanceMapView/index.android.js b/src/components/DistanceMapView/index.android.tsx
similarity index 73%
rename from src/components/DistanceMapView/index.android.js
rename to src/components/DistanceMapView/index.android.tsx
index fa40bd50673e..38e92163c3eb 100644
--- a/src/components/DistanceMapView/index.android.js
+++ b/src/components/DistanceMapView/index.android.tsx
@@ -1,18 +1,15 @@
import React, {useState} from 'react';
import {View} from 'react-native';
-import _ from 'underscore';
import BlockingView from '@components/BlockingViews/BlockingView';
import * as Expensicons from '@components/Icon/Expensicons';
import MapView from '@components/MapView';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
-import * as distanceMapViewPropTypes from './distanceMapViewPropTypes';
+import DistanceMapViewProps from './types';
-function DistanceMapView(props) {
+function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) {
const styles = useThemeStyles();
- const StyleUtils = useStyleUtils();
const [isMapReady, setIsMapReady] = useState(false);
const {isOffline} = useNetwork();
const {translate} = useLocalize();
@@ -21,7 +18,7 @@ function DistanceMapView(props) {
<>
{
if (isMapReady) {
return;
@@ -30,7 +27,7 @@ function DistanceMapView(props) {
}}
/>
{!isMapReady && (
-
+
;
-}
-
-DistanceMapView.propTypes = distanceMapViewPropTypes.propTypes;
-DistanceMapView.defaultProps = distanceMapViewPropTypes.defaultProps;
-DistanceMapView.displayName = 'DistanceMapView';
-
-export default DistanceMapView;
diff --git a/src/components/DistanceMapView/index.tsx b/src/components/DistanceMapView/index.tsx
new file mode 100644
index 000000000000..2abdc29865b0
--- /dev/null
+++ b/src/components/DistanceMapView/index.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import MapView from '@components/MapView';
+import DistanceMapViewProps from './types';
+
+function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return ;
+}
+
+DistanceMapView.displayName = 'DistanceMapView';
+
+export default DistanceMapView;
diff --git a/src/components/DistanceMapView/types.ts b/src/components/DistanceMapView/types.ts
new file mode 100644
index 000000000000..5ab3dbd8238e
--- /dev/null
+++ b/src/components/DistanceMapView/types.ts
@@ -0,0 +1,8 @@
+import {StyleProp, ViewStyle} from 'react-native';
+import type {MapViewProps} from '@components/MapView/MapViewTypes';
+
+type DistanceMapViewProps = MapViewProps & {
+ overlayStyle?: StyleProp;
+};
+
+export default DistanceMapViewProps;
diff --git a/src/components/MapView/index.tsx b/src/components/MapView/index.tsx
index f273845fe4c0..d9da7f1dfea3 100644
--- a/src/components/MapView/index.tsx
+++ b/src/components/MapView/index.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import MapView from './MapView';
-import {ComponentProps} from './types';
+import type {MapViewProps} from './MapViewTypes';
-function MapViewComponent(props: ComponentProps) {
+function MapViewComponent(props: MapViewProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
return ;
}
diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js
index a559e876af18..8ed6d0746438 100644
--- a/src/components/MoneyReportHeader.js
+++ b/src/components/MoneyReportHeader.js
@@ -44,6 +44,9 @@ const propTypes = {
/** The role of the current user in the policy */
role: PropTypes.string,
+
+ /** Whether Scheduled Submit is turned on for this policy */
+ isHarvestingEnabled: PropTypes.bool,
}),
/** The chat report this report is linked to */
@@ -70,7 +73,9 @@ const defaultProps = {
session: {
email: null,
},
- policy: {},
+ policy: {
+ isHarvestingEnabled: false,
+ },
};
function MoneyReportHeader({session, personalDetails, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) {
@@ -108,6 +113,12 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableTotal, moneyRequestReport.currency);
const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth);
+ // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on
+ const isWaitingForSubmissionFromCurrentUser = useMemo(
+ () => chatReport.isOwnPolicyExpenseChat && !policy.isHarvestingEnabled,
+ [chatReport.isOwnPolicyExpenseChat, policy.isHarvestingEnabled],
+ );
+
const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)];
if (!ReportUtils.isArchivedRoom(chatReport)) {
threeDotsMenuItems.push({
@@ -164,7 +175,7 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt