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