diff --git a/src/CONST.ts b/src/CONST.ts index bc56e345de7c..639d34808ba8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3131,6 +3131,7 @@ const CONST = { }, MINI_CONTEXT_MENU_MAX_ITEMS: 4, + REPORT_FIELD_TITLE_FIELD_ID: 'text_title', } as const; diff --git a/src/components/ScrollViewWithContext.tsx b/src/components/ScrollViewWithContext.tsx index 478a0555e89e..4d465ed64a74 100644 --- a/src/components/ScrollViewWithContext.tsx +++ b/src/components/ScrollViewWithContext.tsx @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import React, {useMemo, useRef, useState} from 'react'; -import type {NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; +import type {NativeScrollEvent, NativeSyntheticEvent, ScrollViewProps} from 'react-native'; import {ScrollView} from 'react-native'; const MIN_SMOOTH_SCROLL_EVENT_THROTTLE = 16; @@ -16,10 +16,10 @@ const ScrollContext = React.createContext({ }); type ScrollViewWithContextProps = { - onScroll: (event: NativeSyntheticEvent) => void; + onScroll?: (event: NativeSyntheticEvent) => void; children?: React.ReactNode; - scrollEventThrottle: number; -} & Partial; + scrollEventThrottle?: number; +} & Partial; /* * is a wrapper around that provides a ref to the . @@ -54,7 +54,7 @@ function ScrollViewWithContextWithRef({onScroll, scrollEventThrottle, children, {...restProps} ref={scrollViewRef} onScroll={setContextScrollPosition} - scrollEventThrottle={scrollEventThrottle || MIN_SMOOTH_SCROLL_EVENT_THROTTLE} + scrollEventThrottle={scrollEventThrottle ?? MIN_SMOOTH_SCROLL_EVENT_THROTTLE} > {children} diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 7c0b3a6e33e1..410bd5081e25 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -9,8 +9,7 @@ import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; import CONFIG from '@src/CONFIG'; import ONYXKEYS from '@src/ONYXKEYS'; -import type NetworkOnyx from '@src/types/onyx/Network'; -import type UserOnyx from '@src/types/onyx/User'; +import type {Network as NetworkOnyx, User as UserOnyx} from '@src/types/onyx'; import Button from './Button'; import {withNetwork} from './OnyxProvider'; import Switch from './Switch'; @@ -26,7 +25,6 @@ type TestToolMenuProps = TestToolMenuOnyxProps & { /** Network object in Onyx */ network: OnyxEntry; }; - const USER_DEFAULT: UserOnyx = {shouldUseStagingServer: undefined, isSubscribedToNewsletter: false, validated: false, isFromPublicDomain: false, isUsingExpensifyCard: false}; function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) { diff --git a/src/pages/workspace/WorkspacePageWithSections.js b/src/pages/workspace/WorkspacePageWithSections.tsx similarity index 54% rename from src/pages/workspace/WorkspacePageWithSections.js rename to src/pages/workspace/WorkspacePageWithSections.tsx index a51f7861cba5..8817f813a990 100644 --- a/src/pages/workspace/WorkspacePageWithSections.js +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -1,9 +1,9 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; +import type {RouteProp} from '@react-navigation/native'; +import React, {useEffect, useMemo, useRef} from 'react'; +import type {ReactNode} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -11,76 +11,60 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollViewWithContext from '@components/ScrollViewWithContext'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import BankAccount from '@libs/models/BankAccount'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; -import userPropTypes from '@pages/settings/userPropTypes'; import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; +import type {Policy, ReimbursementAccount, User} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; -const propTypes = { - shouldSkipVBBACall: PropTypes.bool, - - /** The text to display in the header */ - headerText: PropTypes.string.isRequired, - - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - +type WorkspacePageWithSectionsOnyxProps = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, + reimbursementAccount: OnyxEntry; /** User Data from Onyx */ - user: userPropTypes, + user: OnyxEntry; +}; - /** Main content of the page */ - children: PropTypes.func, +type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps & + WorkspacePageWithSectionsOnyxProps & { + shouldSkipVBBACall?: boolean; - /** Content to be added as fixed footer */ - footer: PropTypes.element, + /** The text to display in the header */ + headerText: string; - /** The guides call task ID to associate with the workspace page being shown */ - guidesCallTaskID: PropTypes.string, + /** The route object passed to this page from the navigator */ + route: RouteProp<{params: {policyID: string}}>; - /** The route where we navigate when the user press the back button */ - backButtonRoute: PropTypes.string, + /** Main content of the page */ + children: (hasVBA?: boolean, policyID?: string, isUsingECard?: boolean) => ReactNode; - /** Policy values needed in the component */ - policy: PropTypes.shape({ - name: PropTypes.string, - }).isRequired, + /** Content to be added as fixed footer */ + footer?: ReactNode; - /** Option to use the default scroll view */ - shouldUseScrollView: PropTypes.bool, + /** The guides call task ID to associate with the workspace page being shown */ + guidesCallTaskID: string; - /** Option to show the loading page while the API is calling */ - shouldShowLoading: PropTypes.bool, -}; + /** The route where we navigate when the user press the back button */ + backButtonRoute?: Route; -const defaultProps = { - children: () => {}, - user: {}, - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, - footer: null, - guidesCallTaskID: '', - shouldUseScrollView: false, - shouldSkipVBBACall: false, - backButtonRoute: '', - shouldShowLoading: true, -}; + /** Option to use the default scroll view */ + shouldUseScrollView?: boolean; + + /** Option to show the loading page while the API is calling */ + shouldShowLoading?: boolean; + + /** Policy values needed in the component */ + policy: OnyxEntry; + }; -function fetchData(skipVBBACal) { +function fetchData(skipVBBACal?: boolean) { if (skipVBBACal) { return; } @@ -89,28 +73,28 @@ function fetchData(skipVBBACal) { } function WorkspacePageWithSections({ - backButtonRoute, - children, - footer, - guidesCallTaskID, + backButtonRoute = '', + children = () => null, + footer = null, + guidesCallTaskID = '', headerText, policy, - reimbursementAccount, + reimbursementAccount = {}, route, - shouldUseScrollView, - shouldSkipVBBACall, + shouldUseScrollView = false, + shouldSkipVBBACall = false, user, - shouldShowLoading, -}) { + shouldShowLoading = true, +}: WorkspacePageWithSectionsProps) { const styles = useThemeStyles(); useNetwork({onReconnect: () => fetchData(shouldSkipVBBACall)}); - const isLoading = lodashGet(reimbursementAccount, 'isLoading', true); - const achState = lodashGet(reimbursementAccount, 'achData.state', ''); + const isLoading = reimbursementAccount?.isLoading ?? true; + const achState = reimbursementAccount?.achData?.state ?? ''; + const isUsingECard = user?.isUsingExpensifyCard ?? false; + const policyID = route.params.policyID; + const policyName = policy?.name; const hasVBA = achState === BankAccount.STATE.OPEN; - const isUsingECard = lodashGet(user, 'isUsingExpensifyCard', false); - const policyID = lodashGet(route, 'params.policyID'); - const policyName = lodashGet(policy, 'name'); const content = children(hasVBA, policyID, isUsingECard); const firstRender = useRef(true); @@ -123,6 +107,14 @@ function WorkspacePageWithSections({ fetchData(shouldSkipVBBACall); }, [shouldSkipVBBACall]); + const shouldShow = useMemo(() => { + if (isEmptyObject(policy)) { + return true; + } + + return !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy); + }, [policy]); + return ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} - shouldShow={_.isEmpty(policy) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy)} - subtitleKey={_.isEmpty(policy) ? undefined : 'workspace.common.notAuthorized'} + shouldShow={shouldShow} + subtitleKey={isEmptyObject(policy) ? undefined : 'workspace.common.notAuthorized'} > Navigation.goBack(backButtonRoute || ROUTES.WORKSPACE_INITIAL.getRoute(policyID))} + onBackButtonPress={() => Navigation.goBack(backButtonRoute ?? ROUTES.WORKSPACE_INITIAL.getRoute(policyID))} /> {(isLoading || firstRender.current) && shouldShowLoading ? ( @@ -164,18 +156,15 @@ function WorkspacePageWithSections({ ); } -WorkspacePageWithSections.propTypes = propTypes; -WorkspacePageWithSections.defaultProps = defaultProps; WorkspacePageWithSections.displayName = 'WorkspacePageWithSections'; -export default compose( - withOnyx({ +export default withPolicyAndFullscreenLoading( + withOnyx({ user: { key: ONYXKEYS.USER, }, reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, - }), - withPolicyAndFullscreenLoading, -)(WorkspacePageWithSections); + })(WorkspacePageWithSections), +); diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx index 2126409231b8..79ff76204c69 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx @@ -1,14 +1,15 @@ -import type {StackScreenProps} from '@react-navigation/stack'; +import type {RouteProp} from '@react-navigation/native'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; -import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; -import type SCREENS from '@src/SCREENS'; import WorkspaceInvoicesNoVBAView from './WorkspaceInvoicesNoVBAView'; import WorkspaceInvoicesVBAView from './WorkspaceInvoicesVBAView'; -type WorkspaceInvoicesPageProps = StackScreenProps; +/** Defined route object that contains the policyID param, WorkspacePageWithSections is a common component for Workspaces and expect the route prop that includes the policyID */ +type WorkspaceInvoicesPageProps = { + route: RouteProp<{params: {policyID: string}}>; +}; function WorkspaceInvoicesPage({route}: WorkspaceInvoicesPageProps) { const {translate} = useLocalize(); @@ -17,13 +18,13 @@ function WorkspaceInvoicesPage({route}: WorkspaceInvoicesPageProps) { - {(hasVBA: boolean, policyID: string) => ( + {(hasVBA?: boolean, policyID?: string) => ( <> - {!hasVBA && } - {hasVBA && } + {!hasVBA && policyID && } + {hasVBA && policyID && } )}