diff --git a/src/languages/en.ts b/src/languages/en.ts index 37069af3d712..174b52b7bad8 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1363,7 +1363,8 @@ export default { whereYouWork: 'Where do you work?', purpose: { title: 'What do you want to do today?', - error: 'Please make a selection before continuing.', + errorSelection: 'Please make a selection to continue.', + errorContinue: 'Please press continue to get set up.', [CONST.ONBOARDING_CHOICES.EMPLOYER]: 'Get paid back by my employer', [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: "Manage my team's expenses", [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'Track and budget expenses', diff --git a/src/languages/es.ts b/src/languages/es.ts index 572e0730c8a6..a072c84ab36e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1362,7 +1362,8 @@ export default { whereYouWork: '¿Dónde trabajas?', purpose: { title: '¿Qué quieres hacer hoy?', - error: 'Por favor, haga una selección antes de continuar.', + errorSelection: 'Por favor selecciona una opción para continuar.', + errorContinue: 'Por favor, haz click en continuar para configurar tu cuenta.', [CONST.ONBOARDING_CHOICES.EMPLOYER]: 'Cobrar de mi empresa', [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: 'Gestionar los gastos de mi equipo', [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'Controlar y presupuestar gastos', diff --git a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx index ef3f6340f3e4..4b252afeea25 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx @@ -1,14 +1,17 @@ import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; +import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import OnboardingModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/OnboardingModalNavigatorScreenOptions'; import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types'; +import OnboardingRefManager from '@libs/OnboardingRefManager'; import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails'; import OnboardingPurpose from '@pages/OnboardingPurpose'; import OnboardingWork from '@pages/OnboardingWork'; +import CONST from '@src/CONST'; import SCREENS from '@src/SCREENS'; import Overlay from './Overlay'; @@ -18,11 +21,26 @@ function OnboardingModalNavigator() { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useOnboardingLayout(); + const outerViewRef = React.useRef(null); + + const handleOuterClick = useCallback(() => { + OnboardingRefManager.handleOuterClick(); + }, []); + + useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, handleOuterClick, {shouldBubble: true}); + return ( - - + + e.stopPropagation()} + style={styles.OnboardingNavigatorInnerView(shouldUseNarrowLayout)} + > + {/* In the latest Electron version buttons can't be both clickable and draggable. That's why we added this workaround. Because of two Pressable components on the desktop app diff --git a/src/libs/OnboardingRefManager.ts b/src/libs/OnboardingRefManager.ts new file mode 100644 index 000000000000..a32d116348d2 --- /dev/null +++ b/src/libs/OnboardingRefManager.ts @@ -0,0 +1,17 @@ +import React from 'react'; + +type TOnboardingRef = { + handleOuterClick: () => void; +}; + +const onboardingRef = React.createRef(); + +const OnboardingRefManager = { + ref: onboardingRef, + handleOuterClick: () => { + onboardingRef.current?.handleOuterClick(); + }, +}; + +export type {TOnboardingRef}; +export default OnboardingRefManager; diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index fd328cc26cda..31ff883834cc 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -1,4 +1,5 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import {useIsFocused} from '@react-navigation/native'; +import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; @@ -19,6 +20,8 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; +import OnboardingRefManager from '@libs/OnboardingRefManager'; +import type {TOnboardingRef} from '@libs/OnboardingRefManager'; import variables from '@styles/variables'; import * as Welcome from '@userActions/Welcome'; import type {OnboardingPurposeType} from '@src/CONST'; @@ -41,7 +44,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on const {shouldUseNarrowLayout} = useOnboardingLayout(); const [selectedPurpose, setSelectedPurpose] = useState(undefined); const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); - const [error, setError] = useState(false); const theme = useTheme(); useDisableModalDismissOnEscape(); @@ -52,8 +54,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on setSelectedPurpose(onboardingPurposeSelected ?? undefined); }, [onboardingPurposeSelected]); - const errorMessage = error ? 'onboarding.purpose.error' : ''; - const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; const paddingHorizontal = shouldUseNarrowLayout ? styles.ph8 : styles.ph5; @@ -83,6 +83,8 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS); }, [selectedPurpose]); + const [errorMessage, setErrorMessage] = useState<'onboarding.purpose.errorSelection' | 'onboarding.purpose.errorContinue' | ''>(''); + const menuItems: MenuItemProps[] = Object.values(CONST.ONBOARDING_CHOICES).map((choice) => { const translationKey = `onboarding.purpose.${choice}` as const; const isSelected = selectedPurpose === choice; @@ -100,10 +102,22 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on shouldShowRightComponent: isSelected, onPress: () => { Welcome.setOnboardingPurposeSelected(choice); - setError(false); + setErrorMessage(''); }, }; }); + const isFocused = useIsFocused(); + + const handleOuterClick = useCallback(() => { + if (!selectedPurpose) { + setErrorMessage('onboarding.purpose.errorSelection'); + } else { + setErrorMessage('onboarding.purpose.errorContinue'); + } + }, [selectedPurpose]); + + const onboardingLocalRef = useRef(null); + useImperativeHandle(isFocused ? OnboardingRefManager.ref : onboardingLocalRef, () => ({handleOuterClick}), [handleOuterClick]); return ( @@ -133,14 +147,14 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, on buttonText={translate('common.continue')} onSubmit={() => { if (!selectedPurpose) { - setError(true); + setErrorMessage('onboarding.purpose.errorSelection'); return; } - setError(false); + setErrorMessage(''); saveAndNavigate(); }} message={errorMessage} - isAlertVisible={error || Boolean(errorMessage)} + isAlertVisible={Boolean(errorMessage)} containerStyles={[styles.w100, styles.mb5, styles.mh0, paddingHorizontal]} />