-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Concierge AI Feature Promo Modal #92007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mjasikowski
merged 51 commits into
Expensify:main
from
gijoe0295:feat/ai-feature-promo-modal
Jun 18, 2026
Merged
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
60ba12a
feat: ai feature promo modal navigation, basic component and guards
gijoe0295 3b61e1e
add English terms
gijoe0295 b6493ea
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 847a4fb
run translation script
gijoe0295 dc88496
remove deprecated getUrlWithBackToParam
gijoe0295 b6d3d51
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 e9738ef
improve navigation guard to account for explanation modal
gijoe0295 d78c768
add lottie files
gijoe0295 07bdc5a
implement AI features promo modal UI
gijoe0295 b2753a6
updated translations
gijoe0295 d8a8c15
fix lint
gijoe0295 fe201cf
fix lint
gijoe0295 59c7595
use FlatList to work on native
gijoe0295 59cdaa3
updated lotties
gijoe0295 a803989
add dismiss icons and pagination dots
gijoe0295 6317720
pre-dismiss AI promo modal NVP
gijoe0295 77cdff4
fix lint and test
gijoe0295 f37d6c8
fix failed tests
gijoe0295 a70c127
attach ai modal guard to navigation state instead
gijoe0295 7ae14b3
add Beta Badge
gijoe0295 5d0393f
address feedbacks
gijoe0295 0fbe423
handle cases where onboarding value is loaded after promo modal guard
gijoe0295 5b39f3e
keep Beta badge closer
gijoe0295 d669235
keep content still, scroll illustration only
gijoe0295 40d6f5e
change pagination and dismiss icon color
gijoe0295 a5454be
only play animation when focused
gijoe0295 ccdb43a
modal height changes
gijoe0295 da60bc4
only play animation when focused
gijoe0295 88736cd
fix broken layout on native and do not animate pagination dots
gijoe0295 9fb7697
replace new lottie
gijoe0295 e879051
enforce onboarding completed check
gijoe0295 bd33c99
remove unused exports
gijoe0295 d266511
fix lint
gijoe0295 a0b0037
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 8627cb8
replace new lottie
gijoe0295 814d8e9
update lint seatbelt to fix lint because we just renamed the files
gijoe0295 7edb45e
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 a0a21f6
replaced lottie
gijoe0295 7ca8c4d
add a min height so modal height persists accross pages
gijoe0295 de8b4a9
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 82d3b97
exclude ai promo modal from last visted path so it will not appear ag…
gijoe0295 198fcb3
auto play Section lottie
gijoe0295 0526781
fix: width is applied on narrow layout
gijoe0295 5eb9a52
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 14e4d19
pre-render text content to lock modal height to the tallest page
gijoe0295 e4ea212
fix: Android crash because modal is showing too early
gijoe0295 24a9c1b
uncomment the nvp guard
gijoe0295 82e0cd9
update the build agents help link
gijoe0295 5a3137d
Merge branch 'main' of github.com:gijoe0295/App into feat/ai-feature-…
gijoe0295 bcdd2f7
fix typecheck
gijoe0295 66e1632
fix lint
gijoe0295 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import React, {useRef} from 'react'; | ||
| import {View} from 'react-native'; | ||
| import Badge from '@components/Badge'; | ||
| import FeatureTrainingModal from '@components/FeatureTrainingModal'; | ||
| import type {FeatureTrainingModalPageProps} from '@components/FeatureTrainingModal'; | ||
| import LottieAnimations from '@components/LottieAnimations'; | ||
| import Text from '@components/Text'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import usePermissions from '@hooks/usePermissions'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {dismissProductTraining} from '@libs/actions/Welcome'; | ||
| import Log from '@libs/Log'; | ||
| import variables from '@styles/variables'; | ||
| import CONST from '@src/CONST'; | ||
|
|
||
| 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: FeatureTrainingModalPageProps[] = [ | ||
| { | ||
| 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 wasDismissedViaConfirmRef = useRef(false); | ||
|
|
||
| const onConfirm = () => { | ||
| Log.hmmm('[AIFeaturesPromoModal] onConfirm called, recording click dismissal'); | ||
| wasDismissedViaConfirmRef.current = true; | ||
| }; | ||
|
|
||
| const onClose = () => { | ||
| const isCloseButtonDismissal = !wasDismissedViaConfirmRef.current; | ||
| Log.hmmm(`[AIFeaturesPromoModal] onClose called, dismissing product training via ${isCloseButtonDismissal ? 'x' : 'click'}`); | ||
| dismissProductTraining(CONST.AI_FEATURES_PROMO_MODAL, isCloseButtonDismissal); | ||
| }; | ||
|
|
||
| return ( | ||
| <FeatureTrainingModal | ||
| pages={pages} | ||
| onConfirm={onConfirm} | ||
| onClose={onClose} | ||
| width={variables.aiFeaturesPromoModalWidth} | ||
| shouldRenderHTMLDescription | ||
| shouldUseScrollView | ||
| illustrationOuterContainerStyle={styles.p0} | ||
| illustrationAspectRatio={LottieAnimations.SpendAnalysis.w / LottieAnimations.SpendAnalysis.h} | ||
| contentInnerContainerStyles={styles.mb4} | ||
| modalInnerContainerStyle={styles.pt0} | ||
| titleStyles={styles.mb2} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export default AIFeaturesPromoModal; | ||
110 changes: 110 additions & 0 deletions
110
src/components/FeatureTrainingModal/FeatureTrainingModalBody.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import React from 'react'; | ||
| import {View} from 'react-native'; | ||
| import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
| import useStyleUtils from '@hooks/useStyleUtils'; | ||
| import FeatureTrainingModalContent from './FeatureTrainingModalContent'; | ||
| import FeatureTrainingModalIllustration from './FeatureTrainingModalIllustration'; | ||
| import type {BaseFeatureTrainingModalProps, FeatureTrainingModalPageProps} from './index'; | ||
|
|
||
| type FeatureTrainingModalBodyProps = BaseFeatureTrainingModalProps & | ||
| FeatureTrainingModalPageProps & { | ||
| /** Padding for the modal */ | ||
| modalPadding: number; | ||
|
|
||
| /** Whether the modal should be shown again */ | ||
| willShowAgain: boolean; | ||
|
|
||
| /** A callback to call when the modal should be shown again */ | ||
| toggleWillShowAgain: () => void; | ||
|
|
||
| /** A callback to call when we want to close the modal */ | ||
| closeModal: (didPressHelpButton?: boolean) => void; | ||
|
|
||
| /** A callback to call when we want to close the modal and confirm */ | ||
| confirmModal: () => void; | ||
|
|
||
| /** Whether to show the back button to navigate back to the previous page in carousel mode */ | ||
| shouldShowBackButton?: boolean; | ||
|
|
||
| /** A callback to call when we want to navigate back to the previous page in carousel mode */ | ||
| onBack?: () => void; | ||
| }; | ||
|
|
||
| function FeatureTrainingModalBody({ | ||
| illustrationInnerContainerStyle, | ||
| illustrationOuterContainerStyle, | ||
| illustrationAspectRatio: illustrationAspectRatioProp, | ||
| width, | ||
| title = '', | ||
| subtitle = '', | ||
| description = '', | ||
| secondaryDescription = '', | ||
| titleStyles, | ||
| shouldShowDismissModalOption = false, | ||
| confirmText = '', | ||
| helpText = '', | ||
| onHelp = () => {}, | ||
| children, | ||
| contentInnerContainerStyles, | ||
| contentOuterContainerStyles, | ||
| shouldRenderSVG = true, | ||
| shouldRenderHTMLDescription = false, | ||
| shouldShowConfirmationLoader = false, | ||
| canConfirmWhileOffline = true, | ||
| shouldCallOnHelpWhenModalHidden = false, | ||
| helpSentryLabel, | ||
| confirmSentryLabel, | ||
| modalPadding, | ||
| willShowAgain = true, | ||
| toggleWillShowAgain, | ||
| closeModal, | ||
| confirmModal, | ||
| shouldShowBackButton = false, | ||
| onBack, | ||
| ...props | ||
| }: FeatureTrainingModalBodyProps) { | ||
| const StyleUtils = useStyleUtils(); | ||
| const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); | ||
|
|
||
| return ( | ||
| <View style={width && onboardingIsMediumOrLargerScreenWidth ? StyleUtils.getWidthStyle(width) : undefined}> | ||
| <FeatureTrainingModalIllustration | ||
| illustrationAspectRatio={illustrationAspectRatioProp} | ||
| illustrationInnerContainerStyle={illustrationInnerContainerStyle} | ||
| illustrationOuterContainerStyle={illustrationOuterContainerStyle} | ||
| shouldRenderSVG={shouldRenderSVG} | ||
| modalPadding={modalPadding} | ||
| {...props} | ||
| /> | ||
| <FeatureTrainingModalContent | ||
| title={title} | ||
| subtitle={subtitle} | ||
| description={description} | ||
| secondaryDescription={secondaryDescription} | ||
| confirmText={confirmText} | ||
| helpText={helpText} | ||
| onHelp={onHelp} | ||
| shouldCallOnHelpWhenModalHidden={shouldCallOnHelpWhenModalHidden} | ||
| helpSentryLabel={helpSentryLabel} | ||
| confirmSentryLabel={confirmSentryLabel} | ||
| shouldShowDismissModalOption={shouldShowDismissModalOption} | ||
| willShowAgain={willShowAgain} | ||
| toggleWillShowAgain={toggleWillShowAgain} | ||
| closeModal={closeModal} | ||
| confirmModal={confirmModal} | ||
| shouldShowBackButton={shouldShowBackButton} | ||
| onBack={onBack} | ||
| shouldShowConfirmationLoader={shouldShowConfirmationLoader} | ||
| canConfirmWhileOffline={canConfirmWhileOffline} | ||
| titleStyles={titleStyles} | ||
| contentInnerContainerStyles={contentInnerContainerStyles} | ||
| contentOuterContainerStyles={contentOuterContainerStyles} | ||
| shouldRenderHTMLDescription={shouldRenderHTMLDescription} | ||
| > | ||
| {children} | ||
| </FeatureTrainingModalContent> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| export default FeatureTrainingModalBody; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.