diff --git a/assets/images/product-illustrations/receipts-stacked-on-pin.svg b/assets/images/product-illustrations/receipts-stacked-on-pin.svg
new file mode 100644
index 000000000000..4a4e2bd2f2a7
--- /dev/null
+++ b/assets/images/product-illustrations/receipts-stacked-on-pin.svg
@@ -0,0 +1,109 @@
+
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__paperairplane.svg b/assets/images/simple-illustrations/simple-illustration__paperairplane.svg
new file mode 100644
index 000000000000..152a5d10f21c
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__paperairplane.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/CONST.ts b/src/CONST.ts
index 3b23e2a7314a..ddced06f4bac 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -821,6 +821,7 @@ const CONST = {
},
BETAS: {
ALL: 'all',
+ AUTO_SUBMIT: 'autoSubmit',
DEFAULT_ROOMS: 'defaultRooms',
P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests',
SPOTNANA_TRAVEL: 'spotnanaTravel',
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index de46ec5e7623..e5a3ab00fa5d 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -163,6 +163,14 @@ const ONYXKEYS = {
/** This NVP contains the referral banners the user dismissed */
NVP_DISMISSED_REFERRAL_BANNERS: 'nvp_dismissedReferralBanners',
+ /**
+ * This NVP contains if user has ever seen the instant submit explanation modal and user intent to not show the instant submit explanation modal again
+ * undefined : user has never seen the modal
+ * false : user has seen the modal but has not chosen "do not show again"
+ * true : user has seen the modal and does not want to see it again
+ */
+ NVP_DISMISSED_INSTANT_SUBMIT_EXPLANATION: 'nvp_dismissedInstantSubmitExplanation',
+
/** This NVP contains the training modals the user denied showing again */
NVP_HAS_SEEN_TRACK_TRAINING: 'nvp_hasSeenTrackTraining',
@@ -1052,6 +1060,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_RECENT_ATTENDEES]: Attendee[];
[ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean;
[ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean;
+ [ONYXKEYS.NVP_DISMISSED_INSTANT_SUBMIT_EXPLANATION]: boolean;
[ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: OnyxTypes.LastPaymentMethod;
[ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT]: string;
[ONYXKEYS.LAST_EXPORT_METHOD]: OnyxTypes.LastExportMethod;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 4aaa53a0ef6e..ec889fbf57d4 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -2029,6 +2029,7 @@ const ROUTES = {
getRoute: (bossEmail?: string) => `onboarding/test-drive${bossEmail ? `?bossEmail=${encodeURIComponent(bossEmail)}` : ''}` as const,
},
TEST_DRIVE_DEMO_ROOT: 'onboarding/test-drive/demo',
+ AUTO_SUBMIT_MODAL_ROOT: '/auto-submit',
WORKSPACE_CONFIRMATION: {
route: 'workspace/confirmation',
getRoute: (backTo?: string) => getUrlWithBackToParam(`workspace/confirmation`, backTo),
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 9d53fe813d5a..405e4f13b9a0 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -681,7 +681,6 @@ const SCREENS = {
TEST_DRIVE_MODAL: {
ROOT: 'TestDrive_Modal_Root',
},
-
TEST_DRIVE_DEMO: {
ROOT: 'TestDrive_Demo_Root',
},
@@ -697,6 +696,7 @@ const SCREENS = {
DETAILS_ROOT: 'Details_Root',
PROFILE_ROOT: 'Profile_Root',
PROCESS_MONEY_REQUEST_HOLD_ROOT: 'ProcessMoneyRequestHold_Root',
+ AUTO_SUBMIT_ROOT: 'AutoSubmit_Modal_Root',
CHANGE_POLICY_EDUCATIONAL_ROOT: 'ChangePolicyEducational_Root',
REPORT_DESCRIPTION_ROOT: 'Report_Description_Root',
REPORT_PARTICIPANTS: {
diff --git a/src/components/AutoSubmitModal.tsx b/src/components/AutoSubmitModal.tsx
new file mode 100644
index 000000000000..df8cf7bbc3fc
--- /dev/null
+++ b/src/components/AutoSubmitModal.tsx
@@ -0,0 +1,88 @@
+import React, {useCallback} from 'react';
+import {InteractionManager, View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import useLocalize from '@hooks/useLocalize';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useThemeStyles from '@hooks/useThemeStyles';
+import colors from '@styles/theme/colors';
+import variables from '@styles/variables';
+import {dismissInstantSubmitExplanation} from '@userActions/User';
+import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
+import ONYXKEYS from '@src/ONYXKEYS';
+import FeatureTrainingModal from './FeatureTrainingModal';
+import Icon from './Icon';
+import * as Illustrations from './Icon/Illustrations';
+import Text from './Text';
+
+const menuSections = [
+ {
+ icon: Illustrations.PaperAirplane,
+ titleTranslationKey: 'autoSubmitModal.submittedExpensesTitle',
+ descriptionTranslationKey: 'autoSubmitModal.submittedExpensesDescription',
+ },
+ {
+ icon: Illustrations.Pencil,
+ titleTranslationKey: 'autoSubmitModal.pendingExpensesTitle',
+ descriptionTranslationKey: 'autoSubmitModal.pendingExpensesDescription',
+ },
+];
+
+function AutoSubmitModal() {
+ const [dismissedInstantSubmitExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_INSTANT_SUBMIT_EXPLANATION, {canBeMissing: true});
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
+
+ const onClose = useCallback((willShowAgain: boolean) => {
+ InteractionManager.runAfterInteractions(() => {
+ if (!willShowAgain) {
+ dismissInstantSubmitExplanation(true);
+ } else {
+ dismissInstantSubmitExplanation(false);
+ }
+ });
+ }, []);
+
+ return (
+
+ {menuSections.map((section) => (
+
+
+
+ {translate(section.titleTranslationKey as TranslationPaths)}
+ {translate(section.descriptionTranslationKey as TranslationPaths)}
+
+
+ ))}
+
+ );
+}
+
+export default AutoSubmitModal;
+AutoSubmitModal.displayName = 'AutoSubmitModal';
diff --git a/src/components/FeatureTrainingModal.tsx b/src/components/FeatureTrainingModal.tsx
index 372b8c80f299..fa0e1aabadfd 100644
--- a/src/components/FeatureTrainingModal.tsx
+++ b/src/components/FeatureTrainingModal.tsx
@@ -75,7 +75,7 @@ type BaseFeatureTrainingModalProps = {
confirmText: string;
/** A callback to call when user confirms the tutorial */
- onConfirm?: () => void;
+ onConfirm?: (willShowAgain: boolean) => void;
/** A callback to call when modal closes */
onClose?: () => void;
@@ -339,8 +339,8 @@ function FeatureTrainingModal({
if (shouldCloseOnConfirm) {
closeModal();
}
- onConfirm?.();
- }, [shouldCloseOnConfirm, onConfirm, closeModal]);
+ onConfirm?.(willShowAgain);
+ }, [shouldCloseOnConfirm, onConfirm, closeModal, willShowAgain]);
/**
* Extracts values from the non-scraped attribute WEB_PROP_ATTR at build time
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index e3712e8cf23f..cc6b978f182b 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -56,6 +56,7 @@ import MushroomTopHat from '@assets/images/product-illustrations/mushroom-top-ha
import PaymentHands from '@assets/images/product-illustrations/payment-hands.svg';
import ReceiptYellow from '@assets/images/product-illustrations/receipt--yellow.svg';
import ReceiptsSearchYellow from '@assets/images/product-illustrations/receipts-search--yellow.svg';
+import ReceiptsStackedOnPin from '@assets/images/product-illustrations/receipts-stacked-on-pin.svg';
import RocketBlue from '@assets/images/product-illustrations/rocket--blue.svg';
import RocketOrange from '@assets/images/product-illustrations/rocket--orange.svg';
import RocketDude from '@assets/images/product-illustrations/rocket-dude.svg';
@@ -122,6 +123,7 @@ import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustra
import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg';
import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg';
import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg';
+import PaperAirplane from '@assets/images/simple-illustrations/simple-illustration__paperairplane.svg';
import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg';
import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg';
import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__piggybank.svg';
@@ -318,4 +320,6 @@ export {
ReportReceipt,
PlaidCompanyCardDetailLarge,
PlaidCompanyCardDetail,
+ ReceiptsStackedOnPin,
+ PaperAirplane,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 05f99434cfae..3e7e4870341b 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -6672,6 +6672,14 @@ const translations = {
},
callScheduled: 'Call scheduled',
},
+ autoSubmitModal: {
+ title: 'All clear and submitted!',
+ description: 'All warnings and violations has been cleared so:',
+ submittedExpensesTitle: 'These expenses have been submitted',
+ submittedExpensesDescription: 'These expenses have been sent to your approver but can still be edited until they are approved.',
+ pendingExpensesTitle: 'Pending expenses have been moved',
+ pendingExpensesDescription: 'Any pending card expenses have been moved to a separate report until they post.',
+ },
testDrive: {
quickAction: {
takeATwoMinuteTestDrive: 'Take a 2-minute test drive',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 27e5f0874eed..fb508a0be323 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -7198,6 +7198,14 @@ const translations = {
},
callScheduled: 'Llamada programada',
},
+ autoSubmitModal: {
+ title: '¡Todo claro y enviado!',
+ description: 'Se han solucionado todas las advertencias e infracciones, así que:',
+ submittedExpensesTitle: 'Estos gastos han sido enviados',
+ submittedExpensesDescription: 'Estos gastos se han enviado a tu aprobador pero aún se pueden editar hasta que sean aprobados.',
+ pendingExpensesTitle: 'Los gastos pendientes se han movido',
+ pendingExpensesDescription: 'Todo gasto de tarjeta pendiente se ha movido a un informe separado hasta que sea contabilizado.',
+ },
testDrive: {
quickAction: {
takeATwoMinuteTestDrive: 'Haz una proba de 2 minutos',
diff --git a/src/libs/Navigation/AppNavigator/Navigators/FeatureTrainingModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FeatureTrainingModalNavigator.tsx
index 2041f7b0e4a5..acfd68c962fc 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/FeatureTrainingModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/FeatureTrainingModalNavigator.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import {View} from 'react-native';
+import AutoSubmitModal from '@components/AutoSubmitModal';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
@@ -28,6 +29,10 @@ function FeatureTrainingModalNavigator() {
name={SCREENS.CHANGE_POLICY_EDUCATIONAL_ROOT}
component={ChangePolicyEducationalModal}
/>
+
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 41b34f11354c..85d7a2fdcd43 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -54,6 +54,7 @@ const config: LinkingOptions['config'] = {
exact: true,
},
[SCREENS.PROCESS_MONEY_REQUEST_HOLD_ROOT]: ROUTES.PROCESS_MONEY_REQUEST_HOLD.route,
+ [SCREENS.AUTO_SUBMIT_ROOT]: ROUTES.AUTO_SUBMIT_MODAL_ROOT,
[SCREENS.CHANGE_POLICY_EDUCATIONAL_ROOT]: ROUTES.CHANGE_POLICY_EDUCATIONAL.route,
},
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index e41895598c7a..e524ee2b2ff4 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1610,6 +1610,7 @@ type SignInNavigatorParamList = {
type FeatureTrainingNavigatorParamList = {
[SCREENS.FEATURE_TRAINING_ROOT]: undefined;
[SCREENS.PROCESS_MONEY_REQUEST_HOLD_ROOT]: undefined;
+ [SCREENS.AUTO_SUBMIT_ROOT]: undefined;
[SCREENS.CHANGE_POLICY_EDUCATIONAL_ROOT]: undefined;
};
diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts
index 90b9fa6d835d..c9533a2e8f4e 100644
--- a/src/libs/Permissions.ts
+++ b/src/libs/Permissions.ts
@@ -18,6 +18,15 @@ function canUseLinkPreviews(): boolean {
return false;
}
+/**
+ * Checks if the user can use the auto-submit feature
+ * @param betas - The user's beta flags
+ * @returns true if the user can use auto-submit, false otherwise
+ */
+function canUseAutoSubmit(betas: OnyxEntry): boolean {
+ return !!betas?.includes(CONST.BETAS.AUTO_SUBMIT) || canUseAllBetas(betas);
+}
+
function isBetaEnabled(beta: Beta, betas: OnyxEntry): boolean {
// This beta has been released to everyone, but in case user does not have the NVP loaded, we need to return true here.
// Will be removed in this issue https://github.com/Expensify/App/issues/63254
@@ -31,4 +40,5 @@ export default {
canUseLinkPreviews,
isBlockedFromSpotnanaTravel,
isBetaEnabled,
+ canUseAutoSubmit,
};
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 8182b9d5ee47..64328ca671a0 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -1369,6 +1369,14 @@ function dismissTrackTrainingModal() {
});
}
+/**
+ * Dismiss the Auto-Submit explanation modal
+ * @param shouldDismiss Whether the user selected "Don't show again"
+ */
+function dismissInstantSubmitExplanation(shouldDismiss: boolean) {
+ Onyx.merge(ONYXKEYS.NVP_DISMISSED_INSTANT_SUBMIT_EXPLANATION, shouldDismiss);
+}
+
function requestRefund() {
API.write(WRITE_COMMANDS.REQUEST_REFUND, null);
}
@@ -1428,6 +1436,7 @@ export {
closeAccount,
dismissReferralBanner,
dismissTrackTrainingModal,
+ dismissInstantSubmitExplanation,
resendValidateCode,
requestContactMethodValidateCode,
updateNewsletterSubscription,