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 ? (
)}
- {footerContent}
+ {!shouldRenderFooterAboveSubmit && footerContent}
)}
diff --git a/src/components/ValidateCodeActionForm/type.ts b/src/components/ValidateCodeActionForm/type.ts
index a6a089215a4d..035c8f7142ff 100644
--- a/src/components/ValidateCodeActionForm/type.ts
+++ b/src/components/ValidateCodeActionForm/type.ts
@@ -33,6 +33,12 @@ type ValidateCodeActionFormProps = {
/** Ref for validate code form */
forwardedRef: ForwardedRef;
+ /** Whether to show the skip button */
+ shouldShowSkipButton?: boolean;
+
+ /** Function to call when the skip button is pressed */
+ handleSkipButtonPress?: () => void;
+
/** Text for submit button */
submitButtonText?: string;
diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
index 1c68eb459e0c..1ec4164b3d17 100644
--- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
@@ -75,6 +75,12 @@ type ValidateCodeFormProps = {
/** Whether the form is loading or not */
isLoading?: boolean;
+
+ /** Whether to show skip button */
+ shouldShowSkipButton?: boolean;
+
+ /** Function to call when skip button is pressed */
+ handleSkipButtonPress?: () => void;
};
function BaseValidateCodeForm({
@@ -91,6 +97,8 @@ function BaseValidateCodeForm({
hideSubmitButton,
submitButtonText,
isLoading,
+ shouldShowSkipButton = false,
+ handleSkipButtonPress,
}: ValidateCodeFormProps) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
@@ -100,7 +108,7 @@ function BaseValidateCodeForm({
const [formError, setFormError] = useState({});
const [validateCode, setValidateCode] = useState('');
const inputValidateCodeRef = useRef(null);
- const [account = {}] = useOnyx(ONYXKEYS.ACCOUNT);
+ const [account = {}] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
const shouldDisableResendValidateCode = !!isOffline || account?.isLoading;
const focusTimeoutRef = useRef(null);
@@ -277,14 +285,23 @@ function BaseValidateCodeForm({
messages={{0: translate('validateCodeModal.successfulNewCodeRequest')}}
/>
)}
+
clearError()}
style={buttonStyles}
>
+ {shouldShowSkipButton && (
+
+ )}
{!hideSubmitButton && (