11import { useIsFocused } from '@react-navigation/native' ;
2- import React , { useCallback , useEffect } from 'react' ;
2+ import { PUBLIC_DOMAINS_SET , Str } from 'expensify-common' ;
3+ import React , { useCallback , useEffect , useState } from 'react' ;
34import { View } from 'react-native' ;
4- import BlockingView from '@components/BlockingViews/BlockingView ' ;
5+ import AutoEmailLink from '@components/AutoEmailLink ' ;
56import Button from '@components/Button' ;
7+ import FormProvider from '@components/Form/FormProvider' ;
8+ import InputWrapper from '@components/Form/InputWrapper' ;
9+ import type { FormOnyxValues } from '@components/Form/types' ;
610import HeaderWithBackButton from '@components/HeaderWithBackButton' ;
11+ import Icon from '@components/Icon' ;
712import * as Illustrations from '@components/Icon/Illustrations' ;
13+ import OfflineWithFeedback from '@components/OfflineWithFeedback' ;
814import ScreenWrapper from '@components/ScreenWrapper' ;
915import Text from '@components/Text' ;
10- import ValidateCodeForm from '@components/ValidateCodeActionModal/ValidateCodeForm' ;
16+ import TextInput from '@components/TextInput' ;
17+ import useAutoFocusInput from '@hooks/useAutoFocusInput' ;
1118import useLocalize from '@hooks/useLocalize' ;
19+ import useNetwork from '@hooks/useNetwork' ;
1220import useOnyx from '@hooks/useOnyx' ;
1321import useResponsiveLayout from '@hooks/useResponsiveLayout' ;
1422import useThemeStyles from '@hooks/useThemeStyles' ;
15- import AccountUtils from '@libs/AccountUtils ' ;
16- import { openOldDotLink } from '@libs/actions/Link ' ;
17- import { setOnboardingErrorMessage , setOnboardingMergeAccountStepValue , updateOnboardingValuesAndNavigation } from '@libs/actions/Welcome ' ;
23+ import { isMobileSafari } from '@libs/Browser ' ;
24+ import { addErrorMessage } from '@libs/ErrorUtils ' ;
25+ import getOperatingSystem from '@libs/getOperatingSystem ' ;
1826import Navigation from '@libs/Navigation/Navigation' ;
19- import variables from '@styles/variables' ;
20- import { MergeIntoAccountAndLogin } from '@userActions/Session' ;
21- import { resendValidateCode } from '@userActions/User' ;
27+ import { AddWorkEmail } from '@userActions/Session' ;
28+ import { setOnboardingErrorMessage , setOnboardingMergeAccountStepValue } from '@userActions/Welcome' ;
2229import CONST from '@src/CONST' ;
30+ import type { TranslationPaths } from '@src/languages/types' ;
31+ import Log from '@src/libs/Log' ;
2332import ONYXKEYS from '@src/ONYXKEYS' ;
2433import ROUTES from '@src/ROUTES' ;
25- import type { BaseOnboardingWorkEmailValidationProps } from './types' ;
34+ import INPUT_IDS from '@src/types/form/OnboardingWorkEmailForm' ;
35+ import type IconAsset from '@src/types/utils/IconAsset' ;
36+ import type { BaseOnboardingWorkEmailProps } from './types' ;
2637
27- function BaseOnboardingWorkEmailValidation ( { shouldUseNativeStyles} : BaseOnboardingWorkEmailValidationProps ) {
38+ type Item = {
39+ icon : IconAsset ;
40+ titleTranslationKey : TranslationPaths ;
41+ shouldRenderEmail ?: boolean ;
42+ } ;
43+
44+ function BaseOnboardingWorkEmail ( { shouldUseNativeStyles} : BaseOnboardingWorkEmailProps ) {
2845 const styles = useThemeStyles ( ) ;
2946 const { translate} = useLocalize ( ) ;
30- const [ account ] = useOnyx ( ONYXKEYS . ACCOUNT , { canBeMissing : true } ) ;
31- const [ session ] = useOnyx ( ONYXKEYS . SESSION , { canBeMissing : true } ) ;
32- const [ credentials ] = useOnyx ( ONYXKEYS . CREDENTIALS , { canBeMissing : true } ) ;
33- const [ onboardingEmail ] = useOnyx ( ONYXKEYS . FORMS . ONBOARDING_WORK_EMAIL_FORM , { canBeMissing : true } ) ;
34- const workEmail = onboardingEmail ?. onboardingWorkEmail ;
35-
36- const { onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout ( ) ;
3747 const [ onboardingValues ] = useOnyx ( ONYXKEYS . NVP_ONBOARDING , { canBeMissing : true } ) ;
48+ const [ formValue ] = useOnyx ( ONYXKEYS . FORMS . ONBOARDING_WORK_EMAIL_FORM , { canBeMissing : true } ) ;
49+ const workEmail = formValue ?. [ INPUT_IDS . ONBOARDING_WORK_EMAIL ] ;
50+ const [ onboardingErrorMessage ] = useOnyx ( ONYXKEYS . ONBOARDING_ERROR_MESSAGE , { canBeMissing : true } ) ;
3851 const isVsb = onboardingValues && 'signupQualifier' in onboardingValues && onboardingValues . signupQualifier === CONST . ONBOARDING_SIGNUP_QUALIFIERS . VSB ;
3952 const isSmb = onboardingValues ?. signupQualifier === CONST . ONBOARDING_SIGNUP_QUALIFIERS . SMB ;
40- const [ onboardingErrorMessage ] = useOnyx ( ONYXKEYS . ONBOARDING_ERROR_MESSAGE , { canBeMissing : true } ) ;
41- const isValidateCodeFormSubmitting = AccountUtils . isValidateCodeFormSubmitting ( account ) ;
53+ const { onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout ( ) ;
54+ const { inputCallbackRef} = useAutoFocusInput ( ) ;
55+ const [ shouldValidateOnChange , setShouldValidateOnChange ] = useState ( false ) ;
56+ const { isOffline} = useNetwork ( ) ;
57+ const ICON_SIZE = 48 ;
58+ const operatingSystem = getOperatingSystem ( ) ;
4259 const isFocused = useIsFocused ( ) ;
4360
4461 useEffect ( ( ) => {
45- if ( onboardingValues ?. isMergeAccountStepCompleted === undefined ) {
62+ setOnboardingErrorMessage ( '' ) ;
63+ } , [ ] ) ;
64+
65+ useEffect ( ( ) => {
66+ if ( onboardingValues ?. shouldValidate === undefined && onboardingValues ?. isMergeAccountStepCompleted === undefined ) {
4667 return ;
4768 }
4869 setOnboardingErrorMessage ( '' ) ;
49- if ( onboardingValues ?. shouldRedirectToClassicAfterMerge ) {
50- openOldDotLink ( CONST . OLDDOT_URLS . INBOX , true ) ;
70+
71+ if ( onboardingValues ?. shouldValidate ) {
72+ Navigation . navigate ( ROUTES . ONBOARDING_WORK_EMAIL_VALIDATION . getRoute ( ) ) ;
5173 return ;
5274 }
5375 // Once we verify that shouldValidate is false, we need to force replace the screen
@@ -63,90 +85,160 @@ function BaseOnboardingWorkEmailValidation({shouldUseNativeStyles}: BaseOnboardi
6385 }
6486
6587 if ( ! onboardingValues ?. isMergeAccountStepSkipped ) {
66- Navigation . navigate ( ROUTES . ONBOARDING_WORKSPACES . getRoute ( ) , { forceReplace : true } ) ;
88+ Navigation . navigate ( ROUTES . ONBOARDING_PRIVATE_DOMAIN . getRoute ( ) , { forceReplace : true } ) ;
6789 return ;
6890 }
6991
7092 Navigation . navigate ( ROUTES . ONBOARDING_PURPOSE . getRoute ( ) , { forceReplace : true } ) ;
71- } , [ onboardingValues , isVsb , isSmb , isFocused ] ) ;
93+ } , [ onboardingValues ?. shouldValidate , isVsb , isSmb , isFocused , onboardingValues ?. isMergeAccountStepCompleted , onboardingValues ?. isMergeAccountStepSkipped ] ) ;
7294
73- const sendValidateCode = useCallback ( ( ) => {
74- if ( ! credentials ?. login ) {
75- return ;
95+ const submitWorkEmail = useCallback ( ( values : FormOnyxValues < typeof ONYXKEYS . FORMS . ONBOARDING_WORK_EMAIL_FORM > ) => {
96+ AddWorkEmail ( values [ INPUT_IDS . ONBOARDING_WORK_EMAIL ] ) ;
97+ } , [ ] ) ;
98+
99+ const validate = ( values : FormOnyxValues < typeof ONYXKEYS . FORMS . ONBOARDING_WORK_EMAIL_FORM > ) => {
100+ if ( ! shouldValidateOnChange ) {
101+ setShouldValidateOnChange ( true ) ;
76102 }
77- resendValidateCode ( credentials . login ) ;
78- } , [ credentials ?. login ] ) ;
103+ const userEmail = values [ INPUT_IDS . ONBOARDING_WORK_EMAIL ] ;
104+
105+ const errors = { } ;
106+ const emailParts = userEmail . split ( '@' ) ;
107+ const domain = emailParts . at ( 1 ) ?? '' ;
79108
80- const validateAccountAndMerge = useCallback (
81- ( validateCode : string ) => {
82- setOnboardingErrorMessage ( '' ) ;
83- MergeIntoAccountAndLogin ( workEmail , validateCode , session ?. accountID ) ;
109+ if ( ( PUBLIC_DOMAINS_SET . has ( domain . toLowerCase ( ) ) || ! Str . isValidEmail ( userEmail ) ) && ! isOffline ) {
110+ Log . hmmm ( 'User is trying to add an invalid work email' , { userEmail, domain} ) ;
111+ addErrorMessage ( errors , INPUT_IDS . ONBOARDING_WORK_EMAIL , translate ( 'onboarding.workEmailValidationError.publicEmail' ) ) ;
112+ }
113+
114+ if ( isOffline ?? false ) {
115+ addErrorMessage ( errors , INPUT_IDS . ONBOARDING_WORK_EMAIL , translate ( 'onboarding.workEmailValidationError.offline' ) ) ;
116+ }
117+
118+ return errors ;
119+ } ;
120+
121+ const section : Item [ ] = [
122+ {
123+ icon : Illustrations . EnvelopeReceipt ,
124+ titleTranslationKey : 'onboarding.workEmail.explanationModal.descriptionOne' ,
125+ shouldRenderEmail : true ,
84126 } ,
85- [ workEmail , session ?. accountID ] ,
86- ) ;
127+ {
128+ icon : Illustrations . Profile ,
129+ titleTranslationKey : 'onboarding.workEmail.explanationModal.descriptionTwo' ,
130+ } ,
131+ {
132+ icon : Illustrations . Gears ,
133+ titleTranslationKey : 'onboarding.workEmail.explanationModal.descriptionThree' ,
134+ } ,
135+ ] ;
87136
88137 return (
89138 < ScreenWrapper
90- includeSafeAreaPaddingBottom = { false }
91- testID = "BaseOnboardingWorkEmailValidation"
139+ shouldEnableMaxHeight = { ! isMobileSafari ( ) }
140+ shouldAvoidScrollOnVirtualViewport = { ! isMobileSafari ( ) }
141+ includeSafeAreaPaddingBottom = { isOffline }
142+ testID = "BaseOnboardingWorkEmail"
92143 style = { [ styles . defaultModalContainer , shouldUseNativeStyles && styles . pt8 ] }
93144 >
94145 < HeaderWithBackButton
95- shouldShowBackButton = { ! onboardingValues ?. isMergingAccountBlocked }
96- progressBarPercentage = { 15 }
97- onBackButtonPress = { ( ) => {
98- updateOnboardingValuesAndNavigation ( onboardingValues ) ;
99- } }
146+ progressBarPercentage = { 10 }
147+ shouldShowBackButton = { false }
100148 />
101- { onboardingValues ?. isMergingAccountBlocked ? (
102- < View style = { [ styles . flex1 , onboardingIsMediumOrLargerScreenWidth && styles . mt5 , onboardingIsMediumOrLargerScreenWidth ? styles . mh8 : styles . mh5 ] } >
103- < BlockingView
104- icon = { Illustrations . ToddBehindCloud }
105- iconWidth = { variables . modalTopIconWidth }
106- iconHeight = { variables . modalTopIconHeight }
107- title = { translate ( 'onboarding.mergeBlockScreen.title' ) }
108- subtitle = { translate ( 'onboarding.mergeBlockScreen.subtitle' , { workEmail} ) }
109- subtitleStyle = { [ styles . colorMuted ] }
110- />
111- < Button
112- success = { onboardingValues ?. isMergingAccountBlocked }
113- large
114- style = { [ styles . mb5 ] }
115- text = { translate ( 'common.buttonConfirm' ) }
116- onPress = { ( ) => {
117- setOnboardingErrorMessage ( '' ) ;
118- if ( isVsb ) {
119- Navigation . navigate ( ROUTES . ONBOARDING_ACCOUNTING . getRoute ( ) ) ;
120- return ;
121- }
122- Navigation . navigate ( ROUTES . ONBOARDING_PURPOSE . getRoute ( ) ) ;
123- } }
124- />
149+ < FormProvider
150+ style = { [ styles . flexGrow1 , onboardingIsMediumOrLargerScreenWidth && styles . mt5 , onboardingIsMediumOrLargerScreenWidth ? styles . mh8 : styles . mh5 ] }
151+ formID = { ONYXKEYS . FORMS . ONBOARDING_WORK_EMAIL_FORM }
152+ validate = { validate }
153+ onSubmit = { submitWorkEmail }
154+ submitButtonText = { translate ( 'onboarding.workEmail.addWorkEmail' ) }
155+ enabledWhenOffline
156+ submitFlexEnabled
157+ shouldValidateOnBlur = { false }
158+ shouldValidateOnChange = { shouldValidateOnChange }
159+ shouldTrimValues = { false }
160+ footerContent = {
161+ < OfflineWithFeedback
162+ shouldDisplayErrorAbove
163+ style = { styles . mb3 }
164+ errors = { onboardingErrorMessage ? { addWorkEmailError : onboardingErrorMessage } : undefined }
165+ errorRowStyles = { [ styles . mt2 , styles . textWrap ] }
166+ onClose = { ( ) => setOnboardingErrorMessage ( '' ) }
167+ >
168+ < Button
169+ large
170+ text = { translate ( 'common.skip' ) }
171+ testID = "onboardingPrivateEmailSkipButton"
172+ onPress = { ( ) => {
173+ setOnboardingErrorMessage ( '' ) ;
174+
175+ setOnboardingMergeAccountStepValue ( true , true ) ;
176+ } }
177+ />
178+ </ OfflineWithFeedback >
179+ }
180+ shouldRenderFooterAboveSubmit
181+ shouldHideFixErrorsAlert
182+ >
183+ < View >
184+ < View style = { [ onboardingIsMediumOrLargerScreenWidth ? styles . flexRow : styles . flexColumn , styles . mb3 ] } >
185+ < Text style = { styles . textHeadlineH1 } > { translate ( 'onboarding.workEmail.title' ) } </ Text >
186+ </ View >
187+ < View style = { styles . mb2 } >
188+ < Text style = { [ styles . textNormal , styles . colorMuted ] } > { translate ( 'onboarding.workEmail.subtitle' ) } </ Text >
189+ </ View >
190+ < View >
191+ { section . map ( ( item ) => {
192+ return (
193+ < View
194+ key = { item . titleTranslationKey }
195+ style = { [ styles . mt2 , styles . mb3 ] }
196+ >
197+ < View style = { [ styles . flexRow , styles . alignItemsCenter , styles . flex1 ] } >
198+ < Icon
199+ src = { item . icon }
200+ height = { ICON_SIZE }
201+ width = { ICON_SIZE }
202+ additionalStyles = { [ styles . mr3 ] }
203+ />
204+ < View style = { [ styles . flexColumn , styles . flex1 ] } >
205+ { item . shouldRenderEmail ? (
206+ < AutoEmailLink
207+ style = { [ styles . textStrong , styles . lh20 ] }
208+ text = { translate ( item . titleTranslationKey ) }
209+ />
210+ ) : (
211+ < Text style = { [ styles . textStrong , styles . lh20 ] } > { translate ( item . titleTranslationKey ) } </ Text >
212+ ) }
213+ </ View >
214+ </ View >
215+ </ View >
216+ ) ;
217+ } ) }
218+ </ View >
125219 </ View >
126- ) : (
127- < View style = { [ styles . flex1 , onboardingIsMediumOrLargerScreenWidth && styles . mt5 , onboardingIsMediumOrLargerScreenWidth ? styles . mh8 : styles . mh5 ] } >
128- < Text style = { styles . textHeadlineH1 } > { translate ( 'onboarding.workEmailValidation.title' ) } </ Text >
129- < Text style = { [ styles . textNormal , styles . colorMuted , styles . textAlignLeft , styles . mt5 ] } > { translate ( 'onboarding.workEmailValidation.magicCodeSent' , { workEmail} ) } </ Text >
130- < ValidateCodeForm
131- handleSubmitForm = { validateAccountAndMerge }
132- sendValidateCode = { sendValidateCode }
133- validateCodeActionErrorField = "mergeIntoAccountAndLogIn"
134- clearError = { ( ) => setOnboardingErrorMessage ( '' ) }
135- buttonStyles = { [ styles . flex2 , styles . justifyContentEnd , styles . mb5 ] }
136- shouldShowSkipButton
137- handleSkipButtonPress = { ( ) => {
138- setOnboardingErrorMessage ( '' ) ;
139- setOnboardingMergeAccountStepValue ( true , true ) ;
140- } }
141- isLoading = { isValidateCodeFormSubmitting }
142- validateError = { onboardingErrorMessage ? { invalidCodeError : onboardingErrorMessage } : undefined }
220+
221+ < View style = { [ styles . mb4 , styles . pt3 ] } >
222+ < InputWrapper
223+ InputComponent = { TextInput }
224+ // We do not want to auto-focus for mobile platforms
225+ ref = { operatingSystem !== CONST . OS . ANDROID && operatingSystem !== CONST . OS . IOS ? inputCallbackRef : undefined }
226+ name = "fname"
227+ inputID = { INPUT_IDS . ONBOARDING_WORK_EMAIL }
228+ label = { translate ( 'common.workEmail' ) }
229+ aria-label = { translate ( 'common.workEmail' ) }
230+ role = { CONST . ROLE . PRESENTATION }
231+ defaultValue = { workEmail ?? '' }
232+ shouldSaveDraft
233+ maxLength = { CONST . LOGIN_CHARACTER_LIMIT }
234+ spellCheck = { false }
143235 />
144236 </ View >
145- ) }
237+ </ FormProvider >
146238 </ ScreenWrapper >
147239 ) ;
148240}
149241
150- BaseOnboardingWorkEmailValidation . displayName = 'BaseOnboardingWorkEmailValidation ' ;
242+ BaseOnboardingWorkEmail . displayName = 'BaseOnboardingWorkEmail ' ;
151243
152- export default BaseOnboardingWorkEmailValidation ;
244+ export default BaseOnboardingWorkEmail ;
0 commit comments