diff --git a/src/CONST.ts b/src/CONST.ts index 4af1d3c79a45..14fe136d8b83 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5615,6 +5615,9 @@ const CONST = { DEBUG: 'DEBUG', }, }, + + // We need to store this server side error in order to not show the blocking screen when the error is for invalid code + MERGE_ACCOUNT_INVALID_CODE_ERROR: '401 Not authorized - Invalid validateCode', REIMBURSEMENT_ACCOUNT: { DEFAULT_DATA: { achData: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0dae8c858939..b74ba884bd41 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -786,6 +786,8 @@ const ONYXKEYS = { RULES_CUSTOM_FORM_DRAFT: 'rulesCustomFormDraft', DEBUG_DETAILS_FORM: 'debugDetailsForm', DEBUG_DETAILS_FORM_DRAFT: 'debugDetailsFormDraft', + ONBOARDING_WORK_EMAIL_FORM: 'onboardingWorkEmailForm', + ONBOARDING_WORK_EMAIL_FORM_DRAFT: 'onboardingWorkEmailFormDraft', MERGE_ACCOUNT_DETAILS_FORM: 'mergeAccountDetailsForm', MERGE_ACCOUNT_DETAILS_FORM_DRAFT: 'mergeAccountDetailsFormDraft', WORKSPACE_PER_DIEM_FORM: 'workspacePerDiemForm', @@ -890,6 +892,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.RULES_CUSTOM_FORM]: FormTypes.RulesCustomForm; [ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm; [ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm; + [ONYXKEYS.FORMS.ONBOARDING_WORK_EMAIL_FORM]: FormTypes.OnboardingWorkEmailForm; [ONYXKEYS.FORMS.MERGE_ACCOUNT_DETAILS_FORM]: FormTypes.MergeAccountDetailsForm; [ONYXKEYS.FORMS.INTERNATIONAL_BANK_ACCOUNT_FORM]: FormTypes.InternationalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_PER_DIEM_FORM]: FormTypes.WorkspacePerDiemForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1aced37e8fd0..aa43ac7c7ffb 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1879,6 +1879,14 @@ const ROUTES = { route: 'onboarding/join-workspaces', getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/join-workspaces`, backTo), }, + ONBOARDING_WORK_EMAIL: { + route: 'onboarding/work-email', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/work-email`, backTo), + }, + ONBOARDING_WORK_EMAIL_VALIDATION: { + route: 'onboarding/work-email-validation', + getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/work-email-validation`, backTo), + }, WELCOME_VIDEO_ROOT: 'onboarding/welcome-video', EXPLANATION_MODAL_ROOT: 'onboarding/explanation', WORKSPACE_CONFIRMATION: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index aa8d07ff7147..bbb1b7f8b721 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -641,6 +641,8 @@ const SCREENS = { EMPLOYEES: 'Onboarding_Employees', ACCOUNTING: 'Onboarding_Accounting', WORKSPACES: 'Onboarding_Workspaces', + WORK_EMAIL: 'Onboarding_Work_Email', + WORK_EMAIL_VALIDATION: 'Onboarding_Work_Email_Validation', }, WELCOME_VIDEO: { diff --git a/src/components/AutoEmailLink.tsx b/src/components/AutoEmailLink.tsx index d64c665a020f..56eb7d57ac00 100644 --- a/src/components/AutoEmailLink.tsx +++ b/src/components/AutoEmailLink.tsx @@ -18,32 +18,43 @@ type AutoEmailLinkProps = { function AutoEmailLink({text, style}: AutoEmailLinkProps) { const styles = useThemeStyles(); + const emailRegex = COMMON_CONST.REG_EXP.EXTRACT_EMAIL; + const matches = [...text.matchAll(emailRegex)]; + + if (matches.length === 0) { + return {text}; + } + + let lastIndex = 0; + return ( - {text.split(COMMON_CONST.REG_EXP.EXTRACT_EMAIL).map((str, index) => { - if (COMMON_CONST.REG_EXP.EMAIL.test(str)) { - return ( - - {str} - - ); + {matches.flatMap((match, index) => { + const email = match[0]; + const startIndex = match.index ?? 0; + const elements = []; + + // Push plain text before email + if (startIndex > lastIndex) { + elements.push(text.slice(lastIndex, startIndex)); } - return ( - - {str} - + {email} + , ); + + lastIndex = startIndex + email.length; + return elements; })} + {lastIndex < text.length && text.slice(lastIndex)} ); } diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index 2d1eaaf39b18..26e6c7307e6b 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -73,6 +73,9 @@ type FormProviderProps = FormProps, ) { - const [network] = useOnyx(ONYXKEYS.NETWORK); - const [formState] = useOnyx(`${formID}`); - const [draftValues] = useOnyx(`${formID}Draft`); + const [network] = useOnyx(ONYXKEYS.NETWORK, {canBeMissing: true}); + const [formState] = useOnyx(`${formID}`, {canBeMissing: true}); + const [draftValues] = useOnyx(`${formID}Draft`, {canBeMissing: true}); const {preferredLocale, translate} = useLocalize(); const inputRefs = useRef({}); const touchedInputs = useRef>({}); @@ -432,6 +436,7 @@ function FormProvider( errors={errors} isLoading={isLoading} enabledWhenOffline={enabledWhenOffline} + shouldRenderFooterAboveSubmit={shouldRenderFooterAboveSubmit} > {typeof children === 'function' ? children({inputValues}) : children} diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index e3dd5da85aba..aca6a0ef1a66 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -38,6 +38,9 @@ type FormWrapperProps = ChildrenProps & /** Callback to submit the form */ onSubmit: () => void; + /** should render the extra button above submit button */ + shouldRenderFooterAboveSubmit?: boolean; + /** Whether the form is loading */ isLoading?: boolean; @@ -76,6 +79,7 @@ function FormWrapper({ shouldHideFixErrorsAlert = false, disablePressOnEnter = false, isSubmitDisabled = false, + shouldRenderFooterAboveSubmit = false, isLoading = false, shouldScrollToEnd = false, addBottomSafeAreaPadding, @@ -87,7 +91,7 @@ function FormWrapper({ const formRef = useRef(null); const formContentRef = useRef(null); - const [formState] = useOnyx(`${formID}`); + const [formState] = useOnyx(`${formID}`, {canBeMissing: true}); const errorMessage = useMemo(() => (formState ? getLatestErrorMessage(formState) : undefined), [formState]); @@ -168,6 +172,7 @@ function FormWrapper({ isSubmitActionDangerous={isSubmitActionDangerous} disablePressOnEnter={disablePressOnEnter} enterKeyEventListenerPriority={1} + shouldRenderFooterAboveSubmit={shouldRenderFooterAboveSubmit} shouldBlendOpacity={shouldSubmitButtonBlendOpacity} /> ), @@ -196,6 +201,7 @@ function FormWrapper({ submitButtonStylesWithBottomSafeAreaPadding, submitButtonText, submitFlexEnabled, + shouldRenderFooterAboveSubmit, ], ); diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 97e30eb9753b..8f835e36d518 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -164,6 +164,8 @@ type FormProps = { /** Disable press on enter for submit button */ disablePressOnEnter?: boolean; + /** Render extra button above submit button */ + shouldRenderFooterAboveSubmit?: boolean; /** * Determines whether the form should automatically scroll to the end upon rendering or when the value changes. * If `true`, the form will smoothly scroll to the bottom after interactions have completed. diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx index 0c2d28e70c99..9198aa04e63e 100644 --- a/src/components/FormAlertWithSubmitButton.tsx +++ b/src/components/FormAlertWithSubmitButton.tsx @@ -63,6 +63,9 @@ type FormAlertWithSubmitButtonProps = { /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */ enterKeyEventListenerPriority?: number; + /** should render the extra button above submit button */ + shouldRenderFooterAboveSubmit?: boolean; + /** * Whether the button should have a background layer in the color of theme.appBG. * This is needed for buttons that allow content to display under them. @@ -92,6 +95,7 @@ function FormAlertWithSubmitButton({ useSmallerSubmitButtonSize = false, errorMessageStyle, enterKeyEventListenerPriority = 0, + shouldRenderFooterAboveSubmit = false, shouldBlendOpacity = false, addButtonBottomPadding = true, }: FormAlertWithSubmitButtonProps) { @@ -115,6 +119,7 @@ function FormAlertWithSubmitButton({ > {(isOffline: boolean | undefined) => ( + {shouldRenderFooterAboveSubmit && footerContent} {isOffline && !enabledWhenOffline ? (