diff --git a/src/components/FixedFooter.tsx b/src/components/FixedFooter.tsx
index 35fa4d02f5e0..2f09b27f3067 100644
--- a/src/components/FixedFooter.tsx
+++ b/src/components/FixedFooter.tsx
@@ -2,6 +2,8 @@ import type {ReactNode} from 'react';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
+import useKeyboardState from '@hooks/useKeyboardState';
+import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
import useThemeStyles from '@hooks/useThemeStyles';
type FixedFooterProps = {
@@ -13,8 +15,17 @@ type FixedFooterProps = {
};
function FixedFooter({style, children}: FixedFooterProps) {
+ const {isKeyboardShown} = useKeyboardState();
+ const insets = useSafeAreaInsets();
const styles = useThemeStyles();
- return {children};
+
+ if (!children) {
+ return null;
+ }
+
+ const shouldAddBottomPadding = isKeyboardShown || !insets.bottom;
+
+ return {children};
}
FixedFooter.displayName = 'FixedFooter';
diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx
index 27db5687a925..8182ee487a80 100644
--- a/src/components/FormAlertWithSubmitButton.tsx
+++ b/src/components/FormAlertWithSubmitButton.tsx
@@ -79,7 +79,7 @@ function FormAlertWithSubmitButton({
return (
{props.children}
@@ -69,4 +71,5 @@ export {
useBlockedFromConcierge,
useReportActionsDrafts,
useSession,
+ useAccount,
};
diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx
index c93b75bf11ad..0588f31a0a8c 100644
--- a/src/components/ReferralProgramCTA.tsx
+++ b/src/components/ReferralProgramCTA.tsx
@@ -1,43 +1,49 @@
-import React from 'react';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import React, {useEffect} from 'react';
+import type {ViewStyle} from 'react-native';
+import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import * as User from '@userActions/User';
import CONST from '@src/CONST';
import Navigation from '@src/libs/Navigation/Navigation';
-import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type * as OnyxTypes from '@src/types/onyx';
import Icon from './Icon';
import {Close} from './Icon/Expensicons';
import {PressableWithoutFeedback} from './Pressable';
import Text from './Text';
import Tooltip from './Tooltip';
-type ReferralProgramCTAOnyxProps = {
- dismissedReferralBanners: OnyxEntry;
-};
-
-type ReferralProgramCTAProps = ReferralProgramCTAOnyxProps & {
+type ReferralProgramCTAProps = {
referralContentType:
| typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST
| typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT
| typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY
| typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND;
+ style?: ViewStyle;
+ onDismiss?: () => void;
};
-function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: ReferralProgramCTAProps) {
+function ReferralProgramCTA({referralContentType, style, onDismiss}: ReferralProgramCTAProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
+ const {isDismissed, setAsDismissed} = useDismissedReferralBanners({referralContentType});
const handleDismissCallToAction = () => {
- User.dismissReferralBanner(referralContentType);
+ setAsDismissed();
+ onDismiss?.();
};
- if (!referralContentType || dismissedReferralBanners?.[referralContentType]) {
+ const shouldShowBanner = referralContentType && !isDismissed;
+
+ useEffect(() => {
+ if (shouldShowBanner) {
+ return;
+ }
+ onDismiss?.();
+ }, [onDismiss, shouldShowBanner]);
+
+ if (!shouldShowBanner) {
return null;
}
@@ -46,7 +52,7 @@ function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: Ref
onPress={() => {
Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(referralContentType, Navigation.getActiveRouteWithoutParams()));
}}
- style={[styles.w100, styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10, padding: 10}, styles.pl5]}
+ style={[styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10, padding: 10}, styles.pl5, style]}
accessibilityLabel="referral"
role={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
@@ -81,8 +87,4 @@ function ReferralProgramCTA({referralContentType, dismissedReferralBanners}: Ref
);
}
-export default withOnyx({
- dismissedReferralBanners: {
- key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS,
- },
-})(ReferralProgramCTA);
+export default ReferralProgramCTA;
diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx
index eae8169de046..e53823860ce0 100644
--- a/src/components/ScreenWrapper.tsx
+++ b/src/components/ScreenWrapper.tsx
@@ -1,7 +1,7 @@
import {useNavigation} from '@react-navigation/native';
import type {StackNavigationProp} from '@react-navigation/stack';
import type {ForwardedRef, ReactNode} from 'react';
-import React, {forwardRef, useEffect, useRef, useState} from 'react';
+import React, {createContext, forwardRef, useEffect, useMemo, useRef, useState} from 'react';
import type {DimensionValue, StyleProp, ViewStyle} from 'react-native';
import {Keyboard, PanResponder, View} from 'react-native';
import {PickerAvoidingView} from 'react-native-picker-select';
@@ -99,6 +99,8 @@ type ScreenWrapperProps = {
shouldShowOfflineIndicatorInWideScreen?: boolean;
};
+const ScreenWrapperStatusContext = createContext({didScreenTransitionEnd: false});
+
function ScreenWrapper(
{
shouldEnableMaxHeight = false,
@@ -201,6 +203,7 @@ function ScreenWrapper(
}, []);
const isAvoidingViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileSafari());
+ const contextValue = useMemo(() => ({didScreenTransitionEnd}), [didScreenTransitionEnd]);
return (
@@ -251,16 +254,18 @@ function ScreenWrapper(
{isDevelopment && }
- {
- // If props.children is a function, call it to provide the insets to the children.
- typeof children === 'function'
- ? children({
- insets,
- safeAreaPaddingBottomStyle,
- didScreenTransitionEnd,
- })
- : children
- }
+
+ {
+ // If props.children is a function, call it to provide the insets to the children.
+ typeof children === 'function'
+ ? children({
+ insets,
+ safeAreaPaddingBottomStyle,
+ didScreenTransitionEnd,
+ })
+ : children
+ }
+
{isSmallScreenWidth && shouldShowOfflineIndicator && }
{!isSmallScreenWidth && shouldShowOfflineIndicatorInWideScreen && (
(
showConfirmButton = false,
shouldPreventDefaultFocusOnSelectRow = false,
containerStyle,
- isKeyboardShown = false,
disableKeyboardShortcuts = false,
children,
shouldStopPropagation = false,
@@ -88,6 +88,7 @@ function BaseSelectionList(
const isFocused = useIsFocused();
const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT);
const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true);
+ const {isKeyboardShown} = useKeyboardState();
const [itemsToHighlight, setItemsToHighlight] = useState | null>(null);
const itemFocusTimeoutRef = useRef(null);
const [currentPage, setCurrentPage] = useState(1);
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
index 38c5f03fcae6..af2ea3469408 100644
--- a/src/components/SelectionList/types.ts
+++ b/src/components/SelectionList/types.ts
@@ -284,8 +284,8 @@ type BaseSelectionListProps = Partial & {
/** Styles to apply to SelectionList container */
containerStyle?: StyleProp;
- /** Whether keyboard is visible on the screen */
- isKeyboardShown?: boolean;
+ /** Whether focus event should be delayed */
+ shouldDelayFocus?: boolean;
/** Component to display on the right side of each child */
rightHandSideComponent?: ((item: ListItem) => ReactElement | null) | ReactElement | null;
diff --git a/src/hooks/useDismissedReferralBanners.ts b/src/hooks/useDismissedReferralBanners.ts
new file mode 100644
index 000000000000..94ccd0a0b567
--- /dev/null
+++ b/src/hooks/useDismissedReferralBanners.ts
@@ -0,0 +1,29 @@
+import {useOnyx} from 'react-native-onyx';
+import * as User from '@userActions/User';
+import type CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+
+type UseDismissedReferralBannersProps = {
+ referralContentType:
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY
+ | typeof CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND;
+};
+
+function useDismissedReferralBanners({referralContentType}: UseDismissedReferralBannersProps): {isDismissed: boolean; setAsDismissed: () => void} {
+ const [dismissedReferralBanners] = useOnyx(ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS);
+ const isDismissed = dismissedReferralBanners?.[referralContentType] ?? false;
+
+ const setAsDismissed = () => {
+ if (!referralContentType) {
+ return;
+ }
+ // Set the banner as dismissed
+ User.dismissReferralBanner(referralContentType);
+ };
+
+ return {isDismissed, setAsDismissed};
+}
+
+export default useDismissedReferralBanners;
diff --git a/src/hooks/useScreenWrapperTransitionStatus.ts b/src/hooks/useScreenWrapperTransitionStatus.ts
new file mode 100644
index 000000000000..b9e94abfc024
--- /dev/null
+++ b/src/hooks/useScreenWrapperTransitionStatus.ts
@@ -0,0 +1,17 @@
+import {useContext} from 'react';
+import {ScreenWrapperStatusContext} from '@components/ScreenWrapper';
+
+/**
+ * Hook to get the transition status of a screen inside a ScreenWrapper.
+ * Use this hook if you can't get the transition status from the ScreenWrapper itself. Usually when ScreenWrapper is used inside TopTabNavigator.
+ * @returns `didScreenTransitionEnd` flag to indicate if navigation transition ended.
+ */
+export default function useScreenWrapperTranstionStatus() {
+ const value = useContext(ScreenWrapperStatusContext);
+
+ if (value === undefined) {
+ throw new Error("Couldn't find values for screen ScreenWrapper transition status. Are you inside a screen in ScreenWrapper?");
+ }
+
+ return value;
+}
diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx
index 49e53381e040..ab5bd10317be 100644
--- a/src/pages/RoomInvitePage.tsx
+++ b/src/pages/RoomInvitePage.tsx
@@ -192,6 +192,7 @@ function RoomInvitePage({betas, report, policies}: RoomInvitePageProps) {
-
-
+
);
}
diff --git a/src/pages/SearchPage/index.tsx b/src/pages/SearchPage/index.tsx
index 7d2a5bfecbb8..5576f64ba67a 100644
--- a/src/pages/SearchPage/index.tsx
+++ b/src/pages/SearchPage/index.tsx
@@ -1,7 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
import isEmpty from 'lodash/isEmpty';
import React, {useEffect, useMemo, useState} from 'react';
-import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -10,6 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import UserListItem from '@components/SelectionList/UserListItem';
import useDebouncedState from '@hooks/useDebouncedState';
+import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -37,8 +37,6 @@ type SearchPageOnyxProps = {
type SearchPageProps = SearchPageOnyxProps & StackScreenProps;
-type Options = OptionsListUtils.Options & {headerMessage: string};
-
type SearchPageSectionItem = {
data: OptionData[];
shouldShow: boolean;
@@ -51,7 +49,7 @@ const setPerformanceTimersEnd = () => {
Performance.markEnd(CONST.TIMING.SEARCH_RENDER);
};
-const SearchPageFooterInstance = ;
+const SerachPageFooterInstance = ;
function SearchPage({betas, isSearchingForReports, navigation}: SearchPageProps) {
const [isScreenTransitionEnd, setIsScreenTransitionEnd] = useState(false);
@@ -75,8 +73,8 @@ function SearchPage({betas, isSearchingForReports, navigation}: SearchPageProps)
Report.searchInServer(debouncedSearchValue.trim());
}, [debouncedSearchValue]);
- const searchOptions: Options = useMemo(() => {
- if (!areOptionsInitialized) {
+ const searchOptions = useMemo(() => {
+ if (!areOptionsInitialized || !isScreenTransitionEnd) {
return {
recentReports: [],
personalDetails: [],
@@ -91,7 +89,7 @@ function SearchPage({betas, isSearchingForReports, navigation}: SearchPageProps)
const optionList = OptionsListUtils.getSearchOptions(options, '', betas ?? []);
const header = OptionsListUtils.getHeaderMessage(optionList.recentReports.length + optionList.personalDetails.length !== 0, Boolean(optionList.userToInvite), '');
return {...optionList, headerMessage: header};
- }, [areOptionsInitialized, betas, options]);
+ }, [areOptionsInitialized, betas, isScreenTransitionEnd, options]);
const filteredOptions = useMemo(() => {
if (debouncedSearchValue.trim() === '') {
@@ -159,6 +157,8 @@ function SearchPage({betas, isSearchingForReports, navigation}: SearchPageProps)
setIsScreenTransitionEnd(true);
};
+ const {isDismissed} = useDismissedReferralBanners({referralContentType: CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND});
+
return (
- {({safeAreaPaddingBottomStyle}) => (
- <>
-
-
-
- sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY}
- ListItem={UserListItem}
- textInputValue={searchValue}
- textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
- textInputHint={offlineMessage}
- onChangeText={setSearchValue}
- headerMessage={headerMessage}
- headerMessageStyle={headerMessage === translate('common.noResultsFound') ? [themeStyles.ph4, themeStyles.pb5] : undefined}
- onLayout={setPerformanceTimersEnd}
- onSelectRow={selectReport}
- showLoadingPlaceholder={!areOptionsInitialized}
- footerContent={SearchPageFooterInstance}
- isLoadingNewOptions={isSearchingForReports ?? undefined}
- />
-
- >
- )}
+
+
+ sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY}
+ ListItem={UserListItem}
+ textInputValue={searchValue}
+ textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
+ textInputHint={offlineMessage}
+ onChangeText={setSearchValue}
+ headerMessage={headerMessage}
+ headerMessageStyle={headerMessage === translate('common.noResultsFound') ? [themeStyles.ph4, themeStyles.pb5] : undefined}
+ onLayout={setPerformanceTimersEnd}
+ onSelectRow={selectReport}
+ showLoadingPlaceholder={!areOptionsInitialized || !isScreenTransitionEnd}
+ footerContent={!isDismissed && SerachPageFooterInstance}
+ isLoadingNewOptions={isSearchingForReports ?? undefined}
+ />
);
}
diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
index 4bac6d8c018c..3c65f0fa9a96 100644
--- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
+++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
@@ -1,7 +1,6 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {memo, useCallback, useEffect, useMemo} from 'react';
-import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import Button from '@components/Button';
@@ -14,9 +13,11 @@ import SelectCircle from '@components/SelectCircle';
import SelectionList from '@components/SelectionList';
import UserListItem from '@components/SelectionList/UserListItem';
import useDebouncedState from '@hooks/useDebouncedState';
+import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
+import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as OptionsListUtils from '@libs/OptionsListUtils';
@@ -28,9 +29,6 @@ const propTypes = {
/** Beta features list */
betas: PropTypes.arrayOf(PropTypes.string),
- /** An object that holds data about which referral banners have been dismissed */
- dismissedReferralBanners: PropTypes.objectOf(PropTypes.bool),
-
/** Callback to request parent modal to go to next step, which should be split */
onFinish: PropTypes.func.isRequired,
@@ -48,45 +46,28 @@ const propTypes = {
}),
),
- /** Padding bottom style of safe area */
- safeAreaPaddingBottomStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
-
/** The type of IOU report, i.e. bill, request, send */
iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)).isRequired,
/** The request type, ie. manual, scan, distance */
iouRequestType: PropTypes.oneOf(_.values(CONST.IOU.REQUEST_TYPE)).isRequired,
-
- /** Whether the parent screen transition has ended */
- didScreenTransitionEnd: PropTypes.bool,
};
const defaultProps = {
participants: [],
- safeAreaPaddingBottomStyle: {},
betas: [],
- dismissedReferralBanners: {},
- didScreenTransitionEnd: false,
};
-function MoneyTemporaryForRefactorRequestParticipantsSelector({
- betas,
- participants,
- onFinish,
- onParticipantsAdded,
- safeAreaPaddingBottomStyle,
- iouType,
- iouRequestType,
- dismissedReferralBanners,
- didScreenTransitionEnd,
-}) {
+function MoneyTemporaryForRefactorRequestParticipantsSelector({betas, participants, onFinish, onParticipantsAdded, iouType, iouRequestType}) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
const {isOffline} = useNetwork();
const personalDetails = usePersonalDetails();
- const {canUseP2PDistanceRequests} = usePermissions(iouType);
+ const {isDismissed} = useDismissedReferralBanners({referralContentType});
+ const {canUseP2PDistanceRequests} = usePermissions();
+ const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus();
const {options, areOptionsInitialized} = useOptionsList({
shouldInitialize: didScreenTransitionEnd,
});
@@ -106,7 +87,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
*/
const [sections, newChatOptions] = useMemo(() => {
const newSections = [];
- if (!areOptionsInitialized) {
+ if (!areOptionsInitialized || !didScreenTransitionEnd) {
return [newSections, {}];
}
const chatOptions = OptionsListUtils.getFilteredOptions(
@@ -185,6 +166,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
maxParticipantsReached,
personalDetails,
translate,
+ didScreenTransitionEnd,
]);
/**
@@ -289,13 +271,18 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
[shouldShowSplitBillErrorMessage, onFinish, addSingleParticipant, participants],
);
- const footerContent = useMemo(
- () => (
-
- {!dismissedReferralBanners[referralContentType] && (
-
-
-
+ const footerContent = useMemo(() => {
+ if (isDismissed && !shouldShowSplitBillErrorMessage && !participants.length) {
+ return;
+ }
+
+ return (
+ <>
+ {!isDismissed && (
+
)}
{shouldShowSplitBillErrorMessage && (
@@ -316,10 +303,9 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
isDisabled={shouldShowSplitBillErrorMessage}
/>
)}
-
- ),
- [handleConfirmSelection, participants.length, dismissedReferralBanners, referralContentType, shouldShowSplitBillErrorMessage, styles, translate],
- );
+ >
+ );
+ }, [handleConfirmSelection, participants.length, isDismissed, referralContentType, shouldShowSplitBillErrorMessage, styles, translate]);
const itemRightSideComponent = useCallback(
(item) => {
@@ -356,23 +342,21 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
);
return (
- 0 ? safeAreaPaddingBottomStyle : {}]}>
-
-
+
);
}
@@ -381,12 +365,6 @@ MoneyTemporaryForRefactorRequestParticipantsSelector.defaultProps = defaultProps
MoneyTemporaryForRefactorRequestParticipantsSelector.displayName = 'MoneyTemporaryForRefactorRequestParticipantsSelector';
export default withOnyx({
- dismissedReferralBanners: {
- key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS,
- },
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- },
betas: {
key: ONYXKEYS.BETAS,
},
@@ -395,8 +373,6 @@ export default withOnyx({
MoneyTemporaryForRefactorRequestParticipantsSelector,
(prevProps, nextProps) =>
_.isEqual(prevProps.participants, nextProps.participants) &&
- prevProps.didScreenTransitionEnd === nextProps.didScreenTransitionEnd &&
- _.isEqual(prevProps.dismissedReferralBanners, nextProps.dismissedReferralBanners) &&
prevProps.iouRequestType === nextProps.iouRequestType &&
prevProps.iouType === nextProps.iouType &&
_.isEqual(prevProps.betas, nextProps.betas),
diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js
index 51dba5858cb5..24602d527389 100644
--- a/src/pages/iou/request/step/IOURequestStepCurrency.js
+++ b/src/pages/iou/request/step/IOURequestStepCurrency.js
@@ -129,6 +129,7 @@ function IOURequestStepCurrency({
onEntryTransitionEnd={() => optionsSelectorRef.current && optionsSelectorRef.current.focus()}
shouldShowWrapper
testID={IOURequestStepCurrency.displayName}
+ includeSafeAreaPaddingBottom={false}
>
{({didScreenTransitionEnd}) => (
{({didScreenTransitionEnd}) => (
lodashGet(route, 'params.iouType', ''));
@@ -128,8 +125,8 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) {
shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()}
testID={MoneyRequestParticipantsPage.displayName}
>
- {({safeAreaPaddingBottomStyle}) => (
-
+ {({didScreenTransitionEnd}) => (
+ <>
navigateToConfirmationStep(iouType)}
navigateToSplit={() => navigateToConfirmationStep(CONST.IOU.TYPE.SPLIT)}
- safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
iouType={iouType}
isDistanceRequest={isDistanceRequest}
isScanRequest={isScanRequest}
+ didScreenTransitionEnd={didScreenTransitionEnd}
/>
-
+ >
)}
);
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index 57b86bebcde5..dbde94b60e96 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -1,6 +1,6 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
-import React, {useCallback, useMemo, useState} from 'react';
+import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
@@ -13,6 +13,8 @@ import ReferralProgramCTA from '@components/ReferralProgramCTA';
import SelectCircle from '@components/SelectCircle';
import SelectionList from '@components/SelectionList';
import UserListItem from '@components/SelectionList/UserListItem';
+import useDebouncedState from '@hooks/useDebouncedState';
+import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
@@ -36,9 +38,6 @@ const propTypes = {
/** Callback to add participants in MoneyRequestModal */
onAddParticipants: PropTypes.func.isRequired,
- /** An object that holds data about which referral banners have been dismissed */
- dismissedReferralBanners: PropTypes.objectOf(PropTypes.bool),
-
/** Selected participants from MoneyRequestModal with login */
participants: PropTypes.arrayOf(
PropTypes.shape({
@@ -50,43 +49,32 @@ const propTypes = {
}),
),
- /** padding bottom style of safe area */
- safeAreaPaddingBottomStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
-
/** The type of IOU report, i.e. bill, request, send */
iouType: PropTypes.string.isRequired,
/** Whether the money request is a distance request or not */
isDistanceRequest: PropTypes.bool,
+
+ /** Whether the screen transition has ended */
+ didScreenTransitionEnd: PropTypes.bool,
};
const defaultProps = {
- dismissedReferralBanners: {},
participants: [],
- safeAreaPaddingBottomStyle: {},
betas: [],
isDistanceRequest: false,
+ didScreenTransitionEnd: false,
};
-function MoneyRequestParticipantsSelector({
- betas,
- dismissedReferralBanners,
- participants,
- navigateToRequest,
- navigateToSplit,
- onAddParticipants,
- safeAreaPaddingBottomStyle,
- iouType,
- isDistanceRequest,
-}) {
+function MoneyRequestParticipantsSelector({betas, participants, navigateToRequest, navigateToSplit, onAddParticipants, iouType, isDistanceRequest, didScreenTransitionEnd}) {
const {translate} = useLocalize();
const styles = useThemeStyles();
- const [searchTerm, setSearchTerm] = useState('');
+ const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
const {isOffline} = useNetwork();
const personalDetails = usePersonalDetails();
+ const {options, areOptionsInitialized} = useOptionsList({shouldInitialize: didScreenTransitionEnd});
const {canUseP2PDistanceRequests} = usePermissions(iouType);
- const {options, areOptionsInitialized} = useOptionsList();
const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS;
const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached);
@@ -98,7 +86,7 @@ function MoneyRequestParticipantsSelector({
options.reports,
options.personalDetails,
betas,
- searchTerm,
+ debouncedSearchTerm,
participants,
CONST.EXPENSIFY_EMAILS,
@@ -123,7 +111,7 @@ function MoneyRequestParticipantsSelector({
personalDetails: chatOptions.personalDetails,
userToInvite: chatOptions.userToInvite,
};
- }, [options.reports, options.personalDetails, betas, searchTerm, participants, iouType, canUseP2PDistanceRequests, isDistanceRequest]);
+ }, [options.reports, options.personalDetails, betas, debouncedSearchTerm, participants, iouType, canUseP2PDistanceRequests, isDistanceRequest]);
/**
* Returns the sections needed for the OptionsSelector
@@ -134,7 +122,7 @@ function MoneyRequestParticipantsSelector({
const newSections = [];
const formatResults = OptionsListUtils.formatSectionsFromSearchTerm(
- searchTerm,
+ debouncedSearchTerm,
participants,
newChatOptions.recentReports,
newChatOptions.personalDetails,
@@ -172,7 +160,7 @@ function MoneyRequestParticipantsSelector({
}
return newSections;
- }, [maxParticipantsReached, newChatOptions.personalDetails, newChatOptions.recentReports, newChatOptions.userToInvite, participants, personalDetails, searchTerm, translate]);
+ }, [maxParticipantsReached, newChatOptions.personalDetails, newChatOptions.recentReports, newChatOptions.userToInvite, participants, personalDetails, debouncedSearchTerm, translate]);
/**
* Adds a single participant to the request
@@ -247,11 +235,11 @@ function MoneyRequestParticipantsSelector({
OptionsListUtils.getHeaderMessage(
_.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0,
Boolean(newChatOptions.userToInvite),
- searchTerm.trim(),
+ debouncedSearchTerm.trim(),
maxParticipantsReached,
- _.some(participants, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())),
+ _.some(participants, (participant) => participant.searchText.toLowerCase().includes(debouncedSearchTerm.trim().toLowerCase())),
),
- [maxParticipantsReached, newChatOptions, participants, searchTerm],
+ [maxParticipantsReached, newChatOptions, participants, debouncedSearchTerm],
);
// Right now you can't split a request with a workspace and other additional participants
@@ -281,13 +269,19 @@ function MoneyRequestParticipantsSelector({
[shouldShowSplitBillErrorMessage, navigateToSplit, addSingleParticipant, participants.length],
);
- const footerContent = useMemo(
- () => (
+ const {isDismissed} = useDismissedReferralBanners({referralContentType});
+
+ const footerContent = useMemo(() => {
+ if (isDismissed && !shouldShowSplitBillErrorMessage && !participants.length) {
+ return null;
+ }
+ return (
- {!dismissedReferralBanners[referralContentType] && (
-
-
-
+ {!isDismissed && (
+
)}
{shouldShowSplitBillErrorMessage && (
@@ -309,9 +303,8 @@ function MoneyRequestParticipantsSelector({
/>
)}
- ),
- [handleConfirmSelection, participants.length, dismissedReferralBanners, referralContentType, shouldShowSplitBillErrorMessage, styles, translate],
- );
+ );
+ }, [handleConfirmSelection, participants.length, isDismissed, referralContentType, shouldShowSplitBillErrorMessage, styles, translate]);
const itemRightSideComponent = useCallback(
(item) => {
@@ -345,23 +338,21 @@ function MoneyRequestParticipantsSelector({
);
return (
- 0 ? safeAreaPaddingBottomStyle : {}]}>
-
-
+
);
}
@@ -370,9 +361,6 @@ MoneyRequestParticipantsSelector.displayName = 'MoneyRequestParticipantsSelector
MoneyRequestParticipantsSelector.defaultProps = defaultProps;
export default withOnyx({
- dismissedReferralBanners: {
- key: ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS,
- },
betas: {
key: ONYXKEYS.BETAS,
},
diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx
index 3f95c3e02a5b..4a85e01d973a 100644
--- a/src/pages/workspace/WorkspaceInvitePage.tsx
+++ b/src/pages/workspace/WorkspaceInvitePage.tsx
@@ -1,7 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useEffect, useMemo, useState} from 'react';
+import React, {useCallback, useEffect, useMemo, useState} from 'react';
import type {SectionListData} from 'react-native';
-import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -64,7 +63,6 @@ function WorkspaceInvitePage({
invitedEmailsToAccountIDsDraft,
policy,
isLoadingReportData = true,
- didScreenTransitionEnd,
}: WorkspaceInvitePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -72,6 +70,7 @@ function WorkspaceInvitePage({
const [selectedOptions, setSelectedOptions] = useState([]);
const [personalDetails, setPersonalDetails] = useState([]);
const [usersToInvite, setUsersToInvite] = useState([]);
+ const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
const openWorkspaceInvitePage = () => {
const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policyMembers, personalDetailsProp);
Policy.openWorkspaceInvitePage(route.params.policyID, Object.keys(policyMemberEmailsToAccountIDs));
@@ -223,18 +222,16 @@ function WorkspaceInvitePage({
setSelectedOptions(newSelectedOptions);
};
- const validate = (): boolean => {
+ const inviteUser = useCallback(() => {
const errors: Errors = {};
if (selectedOptions.length <= 0) {
errors.noUserSelected = 'true';
}
Policy.setWorkspaceErrors(route.params.policyID, errors);
- return isEmptyObject(errors);
- };
+ const isValid = isEmptyObject(errors);
- const inviteUser = () => {
- if (!validate()) {
+ if (!isValid) {
return;
}
@@ -249,7 +246,7 @@ function WorkspaceInvitePage({
});
Policy.setWorkspaceInviteMembersDraft(route.params.policyID, invitedEmailsToAccountIDs);
Navigation.navigate(ROUTES.WORKSPACE_INVITE_MESSAGE.getRoute(route.params.policyID));
- };
+ }, [route.params.policyID, selectedOptions]);
const [policyName, shouldShowAlertPrompt] = useMemo(() => [policy?.name ?? '', !isEmptyObject(policy?.errors) || !!policy?.alertMessage], [policy]);
@@ -271,11 +268,29 @@ function WorkspaceInvitePage({
return OptionsListUtils.getHeaderMessage(personalDetails.length !== 0, usersToInvite.length > 0, searchValue);
}, [excludedUsers, translate, searchTerm, policyName, usersToInvite, personalDetails.length]);
+ const footerContent = useMemo(
+ () => (
+
+ ),
+ [inviteUser, policy?.alertMessage, selectedOptions.length, shouldShowAlertPrompt, styles, translate],
+ );
+
return (
setDidScreenTransitionEnd(true)}
>
-
-
-
);
diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts
index 98ce460a7669..5b9470c6ca6f 100644
--- a/src/types/onyx/Account.ts
+++ b/src/types/onyx/Account.ts
@@ -1,5 +1,6 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
+import type DismissedReferralBanners from './DismissedReferralBanners';
import type * as OnyxCommon from './OnyxCommon';
type TwoFactorAuthStep = ValueOf | '';
@@ -60,6 +61,7 @@ type Account = {
success?: string;
codesAreCopied?: boolean;
twoFactorAuthStep?: TwoFactorAuthStep;
+ dismissedReferralBanners?: DismissedReferralBanners;
};
export default Account;
diff --git a/tests/perf-test/SearchPage.perf-test.tsx b/tests/perf-test/SearchPage.perf-test.tsx
index ea759a1201b2..33ee900f8b6c 100644
--- a/tests/perf-test/SearchPage.perf-test.tsx
+++ b/tests/perf-test/SearchPage.perf-test.tsx
@@ -9,6 +9,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {measurePerformance} from 'reassure';
import {LocaleContextProvider} from '@components/LocaleContextProvider';
import OptionListContextProvider, {OptionsListContext} from '@components/OptionListContextProvider';
+import {KeyboardStateProvider} from '@components/withKeyboardState';
import type {WithNavigationFocusProps} from '@components/withNavigationFocus';
import type {RootStackParamList} from '@libs/Navigation/types';
import {createOptionList} from '@libs/OptionsListUtils';
@@ -75,6 +76,15 @@ jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType
return WithNavigationFocus;
});
+// mock of useDismissedReferralBanners
+jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ __esModule: true,
+ default: jest.fn(() => ({
+ isDismissed: false,
+ setAsDismissed: () => {},
+ })),
+}));
const getMockedReports = (length = 100) =>
createCollection(
@@ -124,7 +134,7 @@ type SearchPageProps = StackScreenProps
+
({
createNavigationContainerRef: jest.fn(),
}));
+jest.mock('../../src/hooks/useKeyboardState', () => ({
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ __esModule: true,
+ default: jest.fn(() => ({
+ isKeyboardShown: false,
+ keyboardHeight: 0,
+ })),
+}));
+
function SelectionListWrapper({canSelectMultiple}: SelectionListWrapperProps) {
const [selectedIds, setSelectedIds] = useState([]);