From b37654c466679c8bebf90cb6482bd1246316b3fc Mon Sep 17 00:00:00 2001 From: Samran Date: Fri, 2 May 2025 01:13:38 +0500 Subject: [PATCH 1/8] feat: Add register for webinar button --- src/CONST.ts | 6 ++ .../ButtonWithDropdownMenu/types.ts | 3 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + src/libs/TalkToSalesUtils.ts | 37 ++++++++ src/pages/home/HeaderView.tsx | 92 +++++++++++++++---- .../BillingBanner/EarlyDiscountBanner.tsx | 8 +- 7 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 src/libs/TalkToSalesUtils.ts diff --git a/src/CONST.ts b/src/CONST.ts index 91b63bac3552..d3cde7938ede 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1103,6 +1103,7 @@ const CONST = { PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing', MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Merge-Accounts', CONNECT_A_BUSINESS_BANK_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account', + REGISTER_FOR_WEBINAR_URL: 'https://events.zoom.us/eo/Aif1I8qCi1GZ7KnLnd1vwGPmeukSRoPjFpyFAZ2udQWn0-B86e1Z~AggLXsr32QYFjq8BlYLZ5I06Dg', TEST_RECEIPT_URL: `${CLOUDFRONT_URL}/images/fake-receipt__tacotodds.png`, // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', @@ -7043,6 +7044,11 @@ const CONST = { EMBEDDED_DEMO_WHITELIST: ['http://', 'https://', 'about:'] as string[], EMBEDDED_DEMO_IFRAME_TITLE: 'Test Drive', }, + + ONBOARDING_HELP: { + TALK_TO_SALES: 'talkToSales', + REGISTER_FOR_WEBINAR: 'registerForWebinar', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 457c655588ca..820b218f5d45 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -18,6 +18,8 @@ type WorkspaceTaxRatesBulkActionType = DeepValueOf; +type OnboardingHelpType = DeepValueOf; + type DropdownOption = { value: TValueType; text: string; @@ -133,4 +135,5 @@ export type { ButtonWithDropdownMenuProps, WorkspaceTaxRatesBulkActionType, ReportExportType, + OnboardingHelpType, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 469827d2930f..45f625e82ef0 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5000,6 +5000,8 @@ const translations = { scheduleADemo: 'Schedule demo', questionMarkButtonTooltip: 'Get assistance from our team', exploreHelpDocs: 'Explore help docs', + registerForWebinar: 'Register for webinar', + onboardingHelp: 'Onboarding help', }, emojiPicker: { skinTonePickerLabel: 'Change default skin tone', diff --git a/src/languages/es.ts b/src/languages/es.ts index 959df190bd91..485fe77350ee 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5054,6 +5054,8 @@ const translations = { scheduleADemo: 'Programa una demostración', questionMarkButtonTooltip: 'Obtén ayuda de nuestro equipo', exploreHelpDocs: 'Explorar la documentación de ayuda', + registerForWebinar: 'Registrarse para el seminario web', + onboardingHelp: 'Ayuda de incorporación', }, emojiPicker: { skinTonePickerLabel: 'Elige el tono de piel por defecto', diff --git a/src/libs/TalkToSalesUtils.ts b/src/libs/TalkToSalesUtils.ts new file mode 100644 index 000000000000..ba00714096b7 --- /dev/null +++ b/src/libs/TalkToSalesUtils.ts @@ -0,0 +1,37 @@ +import {Close, Phone} from '@components/Icon/Expensicons'; +import CONST from '@src/CONST'; +import getPlatform from './getPlatform'; +import {translateLocal} from './Localize'; + +type GetTalkToSalesDataProps = { + accountID?: number; + introSelected?: {companySize?: string}; + talkToAISales?: {isTalkingToAISales?: boolean; isLoading?: boolean}; + reportID?: string; +}; + +function getTalkToSalesData({accountID, introSelected, talkToAISales, reportID}: GetTalkToSalesDataProps) { + if (!reportID || !accountID) { + return; + } + + const isNativePlatform = getPlatform() === CONST.PLATFORM.IOS || getPlatform() === CONST.PLATFORM.ANDROID; + + if (introSelected?.companySize !== CONST.ONBOARDING_COMPANY_SIZE.MICRO || isNativePlatform) { + return null; + } + + const availableCTAs = [translateLocal('aiSales.getHelp'), translateLocal('aiSales.talkToSales'), translateLocal('aiSales.talkToConcierge')]; + const abTestCtaText = availableCTAs.at(accountID % availableCTAs.length) ?? translateLocal('aiSales.talkToSales'); + + const talkToSaleText = talkToAISales?.isTalkingToAISales ? translateLocal('aiSales.hangUp') : abTestCtaText; + const talkToSalesIcon = talkToAISales?.isTalkingToAISales ? Close : Phone; + + return { + talkToSalesIcon, + talkToSaleText, + abTestCtaText, + }; +} + +export default getTalkToSalesData; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index a29d1c1b43e9..97d49e7c1c95 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -4,11 +4,13 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; +import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; +import {DropdownOption, OnboardingHelpType} from '@components/ButtonWithDropdownMenu/types'; import CaretWrapper from '@components/CaretWrapper'; import ConfirmModal from '@components/ConfirmModal'; import DisplayNames from '@components/DisplayNames'; import Icon from '@components/Icon'; -import {BackArrow, CalendarSolid, DotIndicator, FallbackAvatar} from '@components/Icon/Expensicons'; +import {BackArrow, CalendarSolid, DotIndicator, FallbackAvatar, Monitor} from '@components/Icon/Expensicons'; import LoadingBar from '@components/LoadingBar'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -30,6 +32,7 @@ import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {openExternalLink} from '@libs/actions/Link'; +import {initializeOpenAIRealtime, stopConnection} from '@libs/actions/OpenAI'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; @@ -64,6 +67,7 @@ import { shouldReportShowSubscript, } from '@libs/ReportUtils'; import {shouldShowDiscountBanner} from '@libs/SubscriptionUtils'; +import getTalkToSalesData from '@libs/TalkToSalesUtils'; import EarlyDiscountBanner from '@pages/settings/Subscription/CardSection/BillingBanner/EarlyDiscountBanner'; import FreeTrial from '@pages/settings/Subscription/FreeTrial'; import {joinRoom} from '@userActions/Report'; @@ -75,7 +79,6 @@ import SCREENS from '@src/SCREENS'; import type {Report, ReportAction} from '@src/types/onyx'; import type {Icon as IconType} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import TalkToSalesButton from './TalkToSalesButton'; type HeaderViewProps = { /** Toggles the navigationMenu open and closed */ @@ -118,6 +121,9 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true}); const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {canBeMissing: true}); const [isDismissedDiscountBanner, setIsDismissedDiscountBanner] = useState(false); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); + const [talkToAISales] = useOnyx(ONYXKEYS.TALK_TO_AI_SALES); + const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID}); const {translate} = useLocalize(); const theme = useTheme(); @@ -126,6 +132,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const isGroupChat = isGroupChatReportUtils(report) || isDeprecatedGroupDM(report); const {canUseTalkToAISales, canUseLeftHandBar} = usePermissions(); const shouldShowTalkToSales = !!canUseTalkToAISales && isAdminRoom(report); + const shouldShowRegisterForWebinar = introSelected?.companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO && isAdminRoom(report); const allParticipants = getParticipantsAccountIDsForDisplay(report, false, true, undefined, reportMetadata); const shouldAddEllipsis = allParticipants?.length > CONST.DISPLAY_PARTICIPANTS_LIMIT; @@ -230,12 +237,54 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, /> ); - const talkToSalesButton = ( - - ); + const onboardingHelpDropdownButton = () => { + const options: Array> = []; + const talkToSalesOption = getTalkToSalesData({accountID, introSelected, talkToAISales, reportID: report?.reportID}); + + if (shouldShowTalkToSales && talkToSalesOption) { + options.push({ + text: talkToSalesOption.talkToSaleText, + icon: talkToSalesOption.talkToSalesIcon, + value: CONST.ONBOARDING_HELP.TALK_TO_SALES, + onSelected: () => { + if (!report?.reportID) return; + + if (talkToAISales?.isTalkingToAISales) { + stopConnection(); + } else { + initializeOpenAIRealtime(Number(report.reportID) ?? CONST.DEFAULT_NUMBER_ID, talkToSalesOption.abTestCtaText); + } + }, + }); + } + + if (shouldShowRegisterForWebinar) { + options.push({ + text: translate('getAssistancePage.registerForWebinar'), + icon: Monitor, + value: CONST.ONBOARDING_HELP.REGISTER_FOR_WEBINAR, + onSelected: () => { + openExternalLink(CONST.REGISTER_FOR_WEBINAR_URL); + }, + }); + } + + if (options.length === 0) { + return undefined; + } + return ( + null} + shouldAlwaysShowDropdownMenu + success={false} + buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} + options={options} + isSplitButton={false} + customText={translate('getAssistancePage.onboardingHelp')} + isLoading={talkToAISales?.isLoading} + /> + ); + }; const getActionButtonStyles = () => { if (isChatUsedForOnboarding && shouldShowDiscount) { @@ -358,7 +407,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, )} - {!shouldShowEarlyDiscountBanner && shouldShowTalkToSales && !shouldUseNarrowLayout && talkToSalesButton} + {(shouldShowTalkToSales || shouldShowRegisterForWebinar) && !shouldUseNarrowLayout && onboardingHelpDropdownButton()} {!shouldShowGuideBookingButtonInEarlyDiscountBanner && shouldShowGuideBooking && !shouldUseNarrowLayout && guideBookingButton} {!shouldUseNarrowLayout && !shouldShowDiscount && isChatUsedForOnboarding && ( {!isParentReportLoading && !isLoading && canJoin && shouldUseNarrowLayout && {joinButton}} - {!shouldShowEarlyDiscountBanner && shouldShowTalkToSales && shouldUseNarrowLayout && {talkToSalesButton}} + + {!shouldShowEarlyDiscountBanner && (shouldShowTalkToSales || shouldShowRegisterForWebinar) && shouldUseNarrowLayout && ( + {onboardingHelpDropdownButton()} + )} + {!isLoading && !shouldShowDiscount && isChatUsedForOnboarding && shouldUseNarrowLayout && ( + + )} + + {!shouldShowGuideBookingButtonInEarlyDiscountBanner && !isLoading && shouldShowGuideBooking && shouldUseNarrowLayout && ( {guideBookingButton} )} - {!isLoading && !shouldShowDiscount && isChatUsedForOnboarding && shouldUseNarrowLayout && ( - - )} {!!report && shouldUseNarrowLayout && isOpenTaskReport(report, parentReportAction) && ( @@ -415,7 +469,7 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, {shouldShowEarlyDiscountBanner && ( setIsDismissedDiscountBanner(true)} diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/EarlyDiscountBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/EarlyDiscountBanner.tsx index b4d95c438cdb..175786dd9aa4 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner/EarlyDiscountBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner/EarlyDiscountBanner.tsx @@ -28,13 +28,13 @@ type EarlyDiscountBannerProps = { GuideBookingButton?: React.JSX.Element; /** The talk to sales button to display */ - TalkToSalesButton?: React.JSX.Element; + onboardingHelpDropdownButton?: React.JSX.Element; /** Function to trigger when the discount banner is dismissed */ onDismissedDiscountBanner?: () => void; }; -function EarlyDiscountBanner({isSubscriptionPage, GuideBookingButton, TalkToSalesButton, onDismissedDiscountBanner}: EarlyDiscountBannerProps) { +function EarlyDiscountBanner({isSubscriptionPage, GuideBookingButton, onboardingHelpDropdownButton, onDismissedDiscountBanner}: EarlyDiscountBannerProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -82,7 +82,7 @@ function EarlyDiscountBanner({isSubscriptionPage, GuideBookingButton, TalkToSale const smallScreenStyle = shouldUseNarrowLayout ? [styles.flex0, styles.flexBasis100, styles.justifyContentCenter] : []; return ( - {TalkToSalesButton} + {onboardingHelpDropdownButton} {GuideBookingButton}