Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e05a88c
Reapply "Merge pull request #92007 from gijoe0295/feat/ai-feature-pro…
gijoe0295 Jun 19, 2026
6b39918
Fix search router opens/closes when AI promo modal is up
gijoe0295 Jun 19, 2026
db3dc2a
Fix AI promo modal clipping action buttons in landscape
gijoe0295 Jun 19, 2026
0d5e20c
Fix AI promo carousel rendering blank on mWeb
gijoe0295 Jun 19, 2026
4e05ef2
Fix AI promo carousel skipping middle page on fast swipe
gijoe0295 Jun 19, 2026
e0d7fd3
Suppress AI promo modal while acting as a delegate
gijoe0295 Jun 19, 2026
f29182e
Allow external intents to navigate past the AI promo modal
gijoe0295 Jun 19, 2026
785c8b7
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jun 25, 2026
377ae5c
Migrate AI promo modal to dynamic routes
gijoe0295 Jun 25, 2026
6155a82
extract types and refactor FeatureTrainingContent by breaking into sm…
gijoe0295 Jun 25, 2026
c9866c3
refactor directories and rename files for clarity
gijoe0295 Jun 25, 2026
b9922e0
rename and refactor FeatureTrainingCarousel
gijoe0295 Jun 25, 2026
c0ed02e
fix lint an dknip
gijoe0295 Jun 25, 2026
81e4b2b
fix: improve guard and fix modal style
gijoe0295 Jun 25, 2026
d1cbc6d
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jun 25, 2026
70bd957
Revert "Fix search router opens/closes when AI promo modal is up"
gijoe0295 Jun 25, 2026
976d7b9
Support landscape mode. Fixes https://github.com/Expensify/App/issues…
gijoe0295 Jun 25, 2026
82bdce5
fix https://github.com/Expensify/App/issues/93970
gijoe0295 Jun 25, 2026
4f745ea
remove redundant comments
gijoe0295 Jun 25, 2026
2fec414
resolve feedbacks
gijoe0295 Jun 26, 2026
24fc989
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jun 26, 2026
4a8e035
Reapply https://github.com/Expensify/App/pull/94599
gijoe0295 Jun 26, 2026
23e869a
fix: animation flickers when moving
gijoe0295 Jun 26, 2026
6e1ce64
remove redundant code comments
gijoe0295 Jun 26, 2026
4a7b89e
resolved comments
gijoe0295 Jun 26, 2026
1635d74
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jun 30, 2026
18283e2
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jul 2, 2026
4f54860
update lint seatbelt
gijoe0295 Jul 2, 2026
73dc4c0
Block pausing animation on mobile Chrome
gijoe0295 Jul 2, 2026
34c94c2
Extract modal guard to hook
gijoe0295 Jul 2, 2026
c927ed0
Update tests
gijoe0295 Jul 2, 2026
4bbd420
Allow promo modal to show on top of any screen
gijoe0295 Jul 2, 2026
c31d247
Fix: Animation is broken on swipe on mobile browser
gijoe0295 Jul 2, 2026
e05e6ec
Mock useAIFeaturesPromoModal in tests globally
gijoe0295 Jul 2, 2026
a5fe7f5
Do not show modal on top of share page. Fixes https://github.com/Expe…
gijoe0295 Jul 2, 2026
8ad081f
fix lint
gijoe0295 Jul 3, 2026
7f5684e
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jul 3, 2026
b523f4d
Run formatting
gijoe0295 Jul 3, 2026
369c2f9
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jul 6, 2026
8ec7709
Resolve feedbacks
gijoe0295 Jul 6, 2026
1fd9f8f
Simplify NVP dismissal
gijoe0295 Jul 6, 2026
3260e65
Merge branch 'main' of github.com:gijoe0295/App into fix/ai-feature-p…
gijoe0295 Jul 10, 2026
199b3ea
Fix lint
gijoe0295 Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/animations/CustomAgents.lottie
Binary file not shown.
Binary file added assets/animations/ExpenseAssistant.lottie
Binary file not shown.
Binary file added assets/animations/SpendAnalysis.lottie
Binary file not shown.
1 change: 1 addition & 0 deletions config/eslint/eslint.seatbelt.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@
"../../src/libs/Navigation/PlatformStackNavigation/ScreenLayout.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/ScreenFreezeWrapper/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/libs/Navigation/PlatformStackNavigation/navigationOptions/animation/withAnimation.ts" "@typescript-eslint/no-unsafe-type-assertion" 6
"../../src/libs/Navigation/guards/AIFeaturesPromoGuard.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/libs/Navigation/guards/MigratedUserWelcomeModalGuard.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/libs/Navigation/guards/OnboardingGuard.ts" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/libs/Navigation/helpers/createNormalizedConfigs.ts" "@typescript-eslint/no-deprecated/escape" 1
Expand Down
5 changes: 5 additions & 0 deletions jest/setupAfterEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import Onyx from 'react-native-onyx';

jest.useRealTimers();

jest.mock('@hooks/useAIFeaturesPromoModal', () => ({
__esModule: true,
default: jest.fn(),
}));

// Patch Keyboard.addListener to return a subscription object with .remove() so that
// @react-navigation/bottom-tabs useIsKeyboardShown hook doesn't crash on cleanup.
if (Keyboard && typeof Keyboard.addListener === 'function') {
Expand Down
17 changes: 17 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7266,6 +7266,7 @@ const CONST = {
SCREENS.SAML_SIGN_IN,
SCREENS.VALIDATE_LOGIN,
SCREENS.MIGRATED_USER_WELCOME_MODAL.DYNAMIC_ROOT,
SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT,
SCREENS.MONEY_REQUEST.STEP_SCAN,
SCREENS.DOMAIN.MEMBERS_MOVE_TO_GROUP,
...Object.values(SCREENS.MULTIFACTOR_AUTHENTICATION),
Expand Down Expand Up @@ -7800,6 +7801,14 @@ const CONST = {

MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal',

AI_FEATURES_PROMO_MODAL: 'aiFeaturesPromoModal',

AI_FEATURES_PROMO_LEARN_MORE_URLS: {
SPEND_ANALYSIS: 'https://help.expensify.com/articles/new-expensify/concierge-ai/How-Concierge-Analyzes-Spend',
EXPENSE_ASSISTANT: 'https://help.expensify.com/articles/new-expensify/concierge-ai/Expense-Assistant',
BUILD_AGENTS: 'https://help.expensify.com/articles/new-expensify/ai-agents/Create-Agent-Rules',
},

BASE_LIST_ITEM_TEST_ID: 'base-list-item-',
SELECTION_BUTTON_TEST_ID: 'selection-button-',
PRODUCT_TRAINING_TOOLTIP_NAMES: {
Expand Down Expand Up @@ -8812,6 +8821,14 @@ const CONST = {
ROW: 'DomainMembers-Row',
},
},
FEATURE_TRAINING: {
CLOSE_BUTTON: 'FeatureTraining-CloseButton',
BACK_BUTTON: 'FeatureTraining-BackButton',
},
AI_FEATURES_PROMO_MODAL: {
CONFIRM_BUTTON: 'AIFeaturesPromoModal-ConfirmButton',
HELP_BUTTON: 'AIFeaturesPromoModal-HelpButton',
},
},

DOMAIN: {
Expand Down
1 change: 1 addition & 0 deletions src/NAVIGATORS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
ONBOARDING_MODAL_NAVIGATOR: 'OnboardingModalNavigator',
FEATURE_TRAINING_MODAL_NAVIGATOR: 'FeatureTrainingModalNavigator',
MIGRATED_USER_MODAL_NAVIGATOR: 'MigratedUserModalNavigator',
AI_FEATURES_PROMO_MODAL_NAVIGATOR: 'AIFeaturesPromoModalNavigator',
TEST_DRIVE_DEMO_NAVIGATOR: 'TestDriveDemoNavigator',
REPORTS_SPLIT_NAVIGATOR: 'ReportsSplitNavigator',
SETTINGS_SPLIT_NAVIGATOR: 'SettingsSplitNavigator',
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ const DYNAMIC_ROUTES = {
path: 'migrated-user-welcome',
entryScreens: [SCREENS.HOME, SCREENS.INBOX, SCREENS.REPORT, SCREENS.SEARCH.ROOT, SCREENS.WORKSPACES_LIST, SCREENS.WORKSPACE.PROFILE, SCREENS.SETTINGS.ROOT],
},
AI_FEATURES_PROMO: {
path: 'ai-features-promo',
entryScreens: ['*'],
},
EXPENSE_LIMIT_TYPE_SELECTOR: {
path: 'expense-limit-type',
entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_FLAG_AMOUNTS_OVER],
Expand Down
4 changes: 4 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,10 @@ const SCREENS = {
DYNAMIC_ROOT: 'Dynamic_MigratedUserWelcomeModal_Root',
},

AI_FEATURES_PROMO_MODAL: {
DYNAMIC_ROOT: 'Dynamic_AIFeaturesPromoModal_Root',
},

TEST_DRIVE_DEMO: {
ROOT: 'TestDrive_Demo_Root',
},
Expand Down
107 changes: 107 additions & 0 deletions src/components/AIFeaturesPromoModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';

import {dismissProductTraining} from '@libs/actions/Welcome';
import Navigation from '@libs/Navigation/Navigation';

import variables from '@styles/variables';

import CONST from '@src/CONST';

import React from 'react';
import {View} from 'react-native';

import type {FeatureTrainingContentDataProps} from './FeatureTrainingContent';

import Badge from './Badge';
import CenteredModalLayout from './CenteredModalLayout';
import {FeatureTrainingCarousel} from './FeatureTrainingContent';
import LottieAnimations from './LottieAnimations';
import Text from './Text';

function AIFeaturesPromoModal() {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {isBetaEnabled} = usePermissions();
const canUseCustomAgent = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT);

const customAgentPromoTitle = (
<View style={[styles.dFlex, styles.flexRow]}>
<Text style={[styles.textHeadlineH1, styles.mb2]}>{translate('aiFeaturesPromoModal.customAgents.title')}</Text>
<Badge
isStrong
isCondensed
text={translate('common.beta')}
badgeStyles={styles.mb2}
/>
</View>
);

const pages: FeatureTrainingContentDataProps[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be better to write with a pattern like this (I believe it's in our react best practices):

<FeatureTrainingCarousel>
    <CarouselPage ... />
    <CarouselPage ... />
    {canUseCustomAgent ? <CarouselPage ... />}
</FeatureTrainingCarousel>

@gijoe0295 gijoe0295 Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh is this composition over configuration best practice?. Can I defer this refactoring to a followup PR? We need to refactor the whole FeatureTraningContent tree as well. Plus we have some internal shared logics among the carousel pages which need investigation.

cc @ShridharGoel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can see we have it detailed over here: https://github.com/Expensify/App/blob/main/.claude/skills/coding-standards/rules/clean-react-1-composition-over-config.md

I didn't know if this was the first PR implementing this or not. If it's not, I'm totally down with it being refactored in a followup PR.

{
animation: LottieAnimations.SpendAnalysis,
title: translate('aiFeaturesPromoModal.spendAnalysis.title'),
subtitle: translate('aiFeaturesPromoModal.subtitle'),
description: translate('aiFeaturesPromoModal.spendAnalysis.description'),
confirmText: translate('common.next'),
},
{
animation: LottieAnimations.ExpenseAssistant,
title: translate('aiFeaturesPromoModal.expenseAssistant.title'),
subtitle: translate('aiFeaturesPromoModal.subtitle'),
description: translate('aiFeaturesPromoModal.expenseAssistant.description'),
confirmText: canUseCustomAgent ? translate('common.next') : translate('aiFeaturesPromoModal.confirmText'),
},
...(canUseCustomAgent
? [
{
animation: LottieAnimations.CustomAgents,
title: customAgentPromoTitle,
subtitle: translate('aiFeaturesPromoModal.subtitle'),
description: translate('aiFeaturesPromoModal.customAgents.description'),
confirmText: translate('aiFeaturesPromoModal.confirmText'),
},
]
: []),
];

const dismissNVP = (isDismissedUsingCloseButton: boolean) => {
dismissProductTraining(CONST.AI_FEATURES_PROMO_MODAL, isDismissedUsingCloseButton);
};

const confirmAndCloseModal = () => {
Navigation.goBack();
dismissNVP(false);
};

const closeModal = () => {
Navigation.goBack();
dismissNVP(true);
};

return (
<CenteredModalLayout
onBackdropPress={closeModal}
width={variables.aiFeaturesPromoModalWidth}
contentStyle={styles.pt0}
>
<FeatureTrainingCarousel
pages={pages}
onConfirm={confirmAndCloseModal}
onClose={closeModal}
width={variables.aiFeaturesPromoModalWidth}
shouldRenderHTMLDescription
shouldUseScrollView
illustrationOuterContainerStyle={styles.p0}
illustrationAspectRatio={LottieAnimations.SpendAnalysis.w / LottieAnimations.SpendAnalysis.h}
contentInnerContainerStyles={styles.mb4}
titleStyles={styles.mb2}
confirmSentryLabel={CONST.SENTRY_LABEL.AI_FEATURES_PROMO_MODAL.CONFIRM_BUTTON}
helpSentryLabel={CONST.SENTRY_LABEL.AI_FEATURES_PROMO_MODAL.HELP_BUTTON}
/>
Comment thread
gijoe0295 marked this conversation as resolved.
</CenteredModalLayout>
);
}

export default AIFeaturesPromoModal;
Loading
Loading