From dc9b0aa8a544c4cc150e84711fd785d12cc365bf Mon Sep 17 00:00:00 2001 From: M Fahru Date: Wed, 21 Feb 2024 07:02:03 -0700 Subject: [PATCH] Merge pull request #17230 from overleaf/mf-tear-down-inr-2 [web] Tear down INR split test GitOrigin-RevId: 90c6ab4666d9281e804b279d697d6c14ffd682d2 --- .../Features/Project/ProjectListController.js | 20 +-- .../Subscription/SubscriptionController.js | 72 ++------ services/web/app/views/project/list-react.pug | 2 - .../subscriptions/interstitial-payment.pug | 3 +- .../interstitial-payment_no_nudge_monthly.pug | 3 +- .../interstitial-payment_nudge_annual.pug | 3 +- .../web/app/views/subscriptions/plans.pug | 3 +- .../web/frontend/extracted-translations.json | 2 - .../notifications/ads/inr-banner.tsx | 168 +++++------------- .../notifications/user-notifications.tsx | 16 +- .../project-list/inr-banner.stories.tsx | 10 ++ services/web/locales/da.json | 1 - services/web/locales/de.json | 1 - services/web/locales/en.json | 2 - services/web/locales/zh-CN.json | 2 - .../components/inr-banner.test.tsx | 84 +++++++++ .../src/Project/ProjectListControllerTests.js | 41 +++++ .../SubscriptionControllerTests.js | 28 +++ 18 files changed, 233 insertions(+), 228 deletions(-) create mode 100644 services/web/frontend/stories/project-list/inr-banner.stories.tsx create mode 100644 services/web/test/frontend/features/project-list/components/inr-banner.test.tsx diff --git a/services/web/app/src/Features/Project/ProjectListController.js b/services/web/app/src/Features/Project/ProjectListController.js index e9f875fe84..5c4bb1b813 100644 --- a/services/web/app/src/Features/Project/ProjectListController.js +++ b/services/web/app/src/Features/Project/ProjectListController.js @@ -363,28 +363,14 @@ async function projectListPage(req, res, next) { 'writefull-integration' ) - let showInrGeoBanner, inrGeoBannerSplitTestName - let inrGeoBannerVariant = 'default' + let showInrGeoBanner = false let recommendedCurrency + if (usersBestSubscription?.type === 'free') { const { countryCode } = await GeoIpLookup.promises.getCurrencyCode(req.ip) - // Split test is kept active, but all users geolocated in India can - // now use the INR currency (See #13507) - const { variant: inrGeoPricingVariant } = - await SplitTestHandler.promises.getAssignment(req, res, 'geo-pricing-inr') if (countryCode === 'IN') { - inrGeoBannerSplitTestName = - inrGeoPricingVariant === 'inr' - ? 'geo-banners-inr-2' - : 'geo-banners-inr-1' - const geoBannerAssignment = await SplitTestHandler.promises.getAssignment( - req, - res, - inrGeoBannerSplitTestName - ) showInrGeoBanner = true - inrGeoBannerVariant = geoBannerAssignment.variant } } @@ -437,8 +423,6 @@ async function projectListPage(req, res, next) { showWritefullPromoBanner, recommendedCurrency, showInrGeoBanner, - inrGeoBannerVariant, - inrGeoBannerSplitTestName, projectDashboardReact: true, // used in navbar groupSsoSetupSuccess, groupSubscriptionsPendingEnrollment: diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 897c0ba514..f5518a56fc 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -38,8 +38,10 @@ async function plansPage(req, res) { if (GeoIpLookup.isValidCurrencyParam(queryCurrency)) { currency = queryCurrency } - const { recommendedCurrency, countryCode, geoPricingINRTestVariant } = - await _getRecommendedCurrency(req, res) + const { recommendedCurrency, countryCode } = await _getRecommendedCurrency( + req, + res + ) if (recommendedCurrency && currency == null) { currency = recommendedCurrency } @@ -65,24 +67,6 @@ async function plansPage(req, res) { usage: getDefault('usage', 'usage', 'enterprise'), } - let showInrGeoBanner, inrGeoBannerSplitTestName - let inrGeoBannerVariant = 'default' - if (countryCode === 'IN') { - inrGeoBannerSplitTestName = - geoPricingINRTestVariant === 'inr' - ? 'geo-banners-inr-2' - : 'geo-banners-inr-1' - const geoBannerAssignment = await SplitTestHandler.promises.getAssignment( - req, - res, - inrGeoBannerSplitTestName - ) - inrGeoBannerVariant = geoBannerAssignment.variant - if (inrGeoBannerVariant !== 'default') { - showInrGeoBanner = true - } - } - // annual plans with the free trial option split test - nudge variant const annualTrialsAssignment = await SplitTestHandler.promises.getAssignment( req, @@ -93,13 +77,8 @@ async function plansPage(req, res) { const plansPageViewSegmentation = { currency: recommendedCurrency, countryCode, - 'geo-pricing-inr-group': geoPricingINRTestVariant, - 'geo-pricing-inr-page': currency === 'INR' ? 'inr' : 'default', 'annual-trials': annualTrialsAssignment.variant, } - if (inrGeoBannerSplitTestName) { - plansPageViewSegmentation[inrGeoBannerSplitTestName] = inrGeoBannerVariant - } AnalyticsManager.recordEventForSession( req.session, @@ -122,7 +101,7 @@ async function plansPage(req, res) { groupPlanModalDefaults, initialLocalizedGroupPrice: SubscriptionHelper.generateInitialLocalizedGroupPrice(currency), - showInrGeoBanner, + showInrGeoBanner: countryCode === 'IN', annualTrialsAssignment: annualTrialsAssignment?.variant, }) } @@ -233,8 +212,10 @@ async function userSubscriptionPage(req, res) { async function interstitialPaymentPage(req, res) { const user = SessionManager.getSessionUser(req.session) - const { recommendedCurrency, countryCode, geoPricingINRTestVariant } = - await _getRecommendedCurrency(req, res) + const { recommendedCurrency, countryCode } = await _getRecommendedCurrency( + req, + res + ) const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(user) @@ -244,24 +225,6 @@ async function interstitialPaymentPage(req, res) { if (hasSubscription) { res.redirect('/user/subscription?hasSubscription=true') } else { - let showInrGeoBanner, inrGeoBannerSplitTestName - let inrGeoBannerVariant = 'default' - if (countryCode === 'IN') { - inrGeoBannerSplitTestName = - geoPricingINRTestVariant === 'inr' - ? 'geo-banners-inr-2' - : 'geo-banners-inr-1' - const geoBannerAssignment = await SplitTestHandler.promises.getAssignment( - req, - res, - inrGeoBannerSplitTestName - ) - inrGeoBannerVariant = geoBannerAssignment.variant - if (inrGeoBannerVariant !== 'default') { - showInrGeoBanner = true - } - } - // annual plans with the free trial option split test - nudge variant const annualTrialsAssignment = await SplitTestHandler.promises.getAssignment(req, res, 'annual-trials') @@ -269,14 +232,8 @@ async function interstitialPaymentPage(req, res) { const paywallPlansPageViewSegmentation = { currency: recommendedCurrency, countryCode, - 'geo-pricing-inr-group': geoPricingINRTestVariant, - 'geo-pricing-inr-page': recommendedCurrency === 'INR' ? 'inr' : 'default', 'annual-trials': annualTrialsAssignment.variant, } - if (inrGeoBannerSplitTestName) { - paywallPlansPageViewSegmentation[inrGeoBannerSplitTestName] = - inrGeoBannerVariant - } AnalyticsManager.recordEventForSession( req.session, 'paywall-plans-page-view', @@ -304,7 +261,7 @@ async function interstitialPaymentPage(req, res) { recommendedCurrency, interstitialPaymentConfig, showSkipLink, - showInrGeoBanner, + showInrGeoBanner: countryCode === 'IN', }) } } @@ -621,14 +578,6 @@ async function _getRecommendedCurrency(req, res) { const currencyLookup = await GeoIpLookup.promises.getCurrencyCode(ip) const countryCode = currencyLookup.countryCode let recommendedCurrency = currencyLookup.currencyCode - // for #12703 - // Split test is kept active, but all users geolocated in India can - // now use the INR currency (See #13507) - const assignmentINR = await SplitTestHandler.promises.getAssignment( - req, - res, - 'geo-pricing-inr' - ) if (['BRL', 'MXN', 'COP', 'CLP', 'PEN'].includes(recommendedCurrency)) { recommendedCurrency = GeoIpLookup.DEFAULT_CURRENCY_CODE @@ -637,7 +586,6 @@ async function _getRecommendedCurrency(req, res) { return { recommendedCurrency, countryCode, - geoPricingINRTestVariant: assignmentINR?.variant, } } diff --git a/services/web/app/views/project/list-react.pug b/services/web/app/views/project/list-react.pug index cc4f81eb59..07a2feea48 100644 --- a/services/web/app/views/project/list-react.pug +++ b/services/web/app/views/project/list-react.pug @@ -29,8 +29,6 @@ block append meta meta(name="ol-showWritefullPromoBanner" data-type="boolean" content=showWritefullPromoBanner) meta(name="ol-groupsAndEnterpriseBannerVariant" data-type="string" content=groupsAndEnterpriseBannerVariant) meta(name="ol-showInrGeoBanner" data-type="boolean" content=showInrGeoBanner) - meta(name="ol-inrGeoBannerVariant" data-type="string" content=inrGeoBannerVariant) - meta(name="ol-inrGeoBannerSplitTestName" data-type="string" content=inrGeoBannerSplitTestName) meta(name="ol-recommendedCurrency" data-type="string" content=recommendedCurrency) meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment) meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription) diff --git a/services/web/app/views/subscriptions/interstitial-payment.pug b/services/web/app/views/subscriptions/interstitial-payment.pug index 172308a04a..31a0fe9985 100644 --- a/services/web/app/views/subscriptions/interstitial-payment.pug +++ b/services/web/app/views/subscriptions/interstitial-payment.pug @@ -18,7 +18,8 @@ block content .plans .container if showInrGeoBanner - div.alert.alert-success.text-centered !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} + div.notification.notification-type-success.text-centered + div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} .row .col-md-12 diff --git a/services/web/app/views/subscriptions/interstitial-payment_no_nudge_monthly.pug b/services/web/app/views/subscriptions/interstitial-payment_no_nudge_monthly.pug index 3c2f712af2..9ceca988d8 100644 --- a/services/web/app/views/subscriptions/interstitial-payment_no_nudge_monthly.pug +++ b/services/web/app/views/subscriptions/interstitial-payment_no_nudge_monthly.pug @@ -18,7 +18,8 @@ block content .plans .container if showInrGeoBanner - div.alert.alert-success.text-centered !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} + div.notification.notification-type-success.text-centered + div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} .row .col-md-12 diff --git a/services/web/app/views/subscriptions/interstitial-payment_nudge_annual.pug b/services/web/app/views/subscriptions/interstitial-payment_nudge_annual.pug index 47f01ff18e..a03931da39 100644 --- a/services/web/app/views/subscriptions/interstitial-payment_nudge_annual.pug +++ b/services/web/app/views/subscriptions/interstitial-payment_nudge_annual.pug @@ -18,7 +18,8 @@ block content .plans .container if showInrGeoBanner - div.alert.alert-success.text-centered !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} + div.notification.notification-type-success.text-centered + div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} .row .col-md-12 diff --git a/services/web/app/views/subscriptions/plans.pug b/services/web/app/views/subscriptions/plans.pug index baeef21b28..981514228a 100644 --- a/services/web/app/views/subscriptions/plans.pug +++ b/services/web/app/views/subscriptions/plans.pug @@ -16,7 +16,8 @@ block content .plans .container(ng-cloak) if showInrGeoBanner - div.alert.alert-success.text-centered !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} + div.notification.notification-type-success.text-centered + div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})} .row .col-md-12 diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index fd3731c233..403458d0e7 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -578,8 +578,6 @@ "increased_compile_timeout": "", "inr_discount_modal_info": "", "inr_discount_modal_title": "", - "inr_discount_offer": "", - "inr_discount_offer_green_banner": "", "insert": "", "insert_column_left": "", "insert_column_right": "", diff --git a/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx b/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx index 078a5f338e..4391dad30f 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx @@ -1,35 +1,12 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { Trans, useTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import usePersistedState from '../../../../../shared/hooks/use-persisted-state' -import Notification from '../notification' import * as eventTracking from '../../../../../infrastructure/event-tracking' import { Modal, Button } from 'react-bootstrap' import AccessibleModal from '../../../../../shared/components/accessible-modal' -import getMeta from '@/utils/meta' -interface VariantContents { - default: string - 'green-banner': string - modal: string -} - -const contentLookup: VariantContents = { - default: 'blue', - 'green-banner': 'green', - modal: 'modal', -} - -type INRBannerProps = { - variant: keyof VariantContents - splitTestName: string -} - -export default function INRBanner({ variant, splitTestName }: INRBannerProps) { +export default function INRBanner() { const { t } = useTranslation() - const newNotificationStyle = getMeta( - 'ol-newNotificationStyle', - false - ) as boolean const [dismissedUntil, setDismissedUntil] = usePersistedState< Date | undefined >(`has_dismissed_inr_banner_until`) @@ -44,38 +21,41 @@ export default function INRBanner({ variant, splitTestName }: INRBannerProps) { } if (!viewEventSent.current) { eventTracking.sendMB('promo-prompt', { - location: variant === 'modal' ? 'dashboard-modal' : 'dashboard-banner', - name: splitTestName, - content: contentLookup[variant], + location: 'dashboard-modal', + name: 'geo-pricing', + content: 'modal', + country: 'IN', }) viewEventSent.current = true } - }, [dismissedUntil, splitTestName, variant]) + }, [dismissedUntil]) const handleClick = useCallback(() => { eventTracking.sendMB('promo-click', { - location: variant === 'modal' ? 'dashboard-modal' : 'dashboard-banner', - name: splitTestName, - content: contentLookup[variant], + location: 'dashboard-modal', + name: 'geo-pricing', + content: 'modal', type: 'click', + country: 'IN', }) setShowModal(false) window.open('/user/subscription/plans') - }, [splitTestName, variant]) + }, []) const bannerDismissed = useCallback(() => { eventTracking.sendMB('promo-dismiss', { - location: variant === 'modal' ? 'dashboard-modal' : 'dashboard-banner', - name: splitTestName, - content: contentLookup[variant], + location: 'dashboard-modal', + name: 'geo-pricing', + content: 'modal', type: 'click', + country: 'IN', }) const until = new Date() until.setDate(until.getDate() + 30) // 30 days setDismissedUntil(until) - }, [setDismissedUntil, splitTestName, variant]) + }, [setDismissedUntil]) const handleHide = useCallback(() => { setShowModal(false) @@ -84,99 +64,47 @@ export default function INRBanner({ variant, splitTestName }: INRBannerProps) { const handleMaybeLater = useCallback(() => { eventTracking.sendMB('promo-click', { - location: variant === 'modal' ? 'dashboard-modal' : 'dashboard-banner', - name: splitTestName, - content: contentLookup[variant], + location: 'dashboard-modal', + name: 'geo-pricing', + content: 'modal', type: 'pause', + country: 'IN', }) setShowModal(false) const until = new Date() until.setDate(until.getDate() + 1) // 1 day setDismissedUntil(until) - }, [setDismissedUntil, splitTestName, variant]) + }, [setDismissedUntil]) if (dismissedUntil && new Date(dismissedUntil) > new Date()) { return null } - if (variant === 'default') { - return ( - ]} // eslint-disable-line react/jsx-key + return ( + + + {t('inr_discount_modal_title')} + + +

+ {t('inr_discount_modal_title')} - } - action={ - - } - /> - ) - } else if (variant === 'green-banner') { - return ( - ,
]} // eslint-disable-line react/jsx-key - values={{ flag: '🇮🇳' }} - shouldUnescape - tOptions={{ interpolation: { escapeValue: true } }} - /> - } - action={ - - } - /> - ) - } else if (variant === 'modal') { - return ( - - - {t('inr_discount_modal_title')} - - -

- {t('inr_discount_modal_title')} -

-

{t('inr_discount_modal_info')}

-
- - - - -
- ) - } else { - return null - } +

+

{t('inr_discount_modal_info')}

+ + + + + + + ) } diff --git a/services/web/frontend/js/features/project-list/components/notifications/user-notifications.tsx b/services/web/frontend/js/features/project-list/components/notifications/user-notifications.tsx index 3d038d3073..58b64cb199 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/user-notifications.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/user-notifications.tsx @@ -44,11 +44,6 @@ function UserNotifications() { ) const showInrGeoBanner = getMeta('ol-showInrGeoBanner', false) - const inrGeoBannerVariant = getMeta('ol-inrGeoBannerVariant', 'default') - const inrGeoBannerSplitTestName = getMeta( - 'ol-inrGeoBannerSplitTestName', - 'unassigned' - ) const writefullIntegrationSplitTestEnabled = isSplitTestEnabled( 'writefull-integration' ) @@ -105,15 +100,8 @@ function UserNotifications() { - {!showInrGeoBanner && !showWritefull && !dismissedWritefull && ( - - )} - {showInrGeoBanner && ( - - )} + {!showWritefull && !dismissedWritefull && } + {showInrGeoBanner && } {writefullIntegrationSplitTestEnabled || user?.writefull?.enabled ? ( { + return +} + +export default { + title: 'Project List / INR Banner', + component: INRBanner, +} diff --git a/services/web/locales/da.json b/services/web/locales/da.json index e1e5f98dfe..6ad740d9a6 100644 --- a/services/web/locales/da.json +++ b/services/web/locales/da.json @@ -701,7 +701,6 @@ "increased_compile_timeout": "Forlænget kompileringstidsgrænse", "indvidual_plans": "Individuelle abonnementer", "info": "Info", - "inr_discount_offer": "Gode nyheder! Du kan nu bruge rupier ₹ til at betale for et Overleaf abonnement, hvilket giver dig en <0>70% rabat på vores Premium-funktioner.", "insert_figure": "Indsæt figur", "insert_from_another_project": "Indsæt fra andet projekt", "insert_from_project_files": "Indsæt fra projektfiler", diff --git a/services/web/locales/de.json b/services/web/locales/de.json index 2e55f3a4ec..59a9c293e2 100644 --- a/services/web/locales/de.json +++ b/services/web/locales/de.json @@ -719,7 +719,6 @@ "increased_compile_timeout": "Zeitlimit beim Kompilieren erhöhen", "indvidual_plans": "Einzelnutzer-Abonnements", "info": "Info", - "inr_discount_offer": "Gute Nachrichten! Du kannst jetzt für dein Overleaf-Abonnement in Rupees ₹ bezahlen und so einen <0>70% Rabatt auf unsere Premiumfunktionen erhalten.", "insert_figure": "Abbildung einfügen", "insert_from_another_project": "Von einem anderen Projekt einfügen", "insert_from_project_files": "Von Projektdateien einfügen", diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 6319d57fe9..b1475a7134 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -845,8 +845,6 @@ "info": "Info", "inr_discount_modal_info": "Get document history, track changes, additional collaborators, and more at Purchasing Power Parity prices.", "inr_discount_modal_title": "70% off all Overleaf premium plans for users in India", - "inr_discount_offer": "Good news! You can now use Rupees ₹ to pay for an Overleaf subscription, giving you a <0>70% discount on our premium features.", - "inr_discount_offer_green_banner": "__flag__ <0>Big news! You qualify for a <0>70% discount on our premium plans because you’re in India. <1/>Get additional collaborators, document history, track changes, and more.", "inr_discount_offer_plans_page_banner": "__flag__ Great news! We’ve applied a 70% discount discount to premium plans for our users in India. Check out the new lower prices below.", "insert": "Insert", "insert_column_left": "Insert column left", diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index 54a15256b1..f31618c9f7 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -855,8 +855,6 @@ "info": "信息", "inr_discount_modal_info": "以平价获取文档历史记录、跟踪更改、更多协作者等功能。", "inr_discount_modal_title": "面向印度用户的所有 Overleaf 高级计划七折优惠", - "inr_discount_offer": "好消息! 您现在可以使用卢比 ₹ 来支付 Overleaf 订阅费用,从而在使用我们的高级功能时享受<0>70% 的折扣。", - "inr_discount_offer_green_banner": "__flag__ <0>好消息!由于您在印度,因此您有资格享受我们的高级计划的<0>70% 折扣。 <1/>获取其他协作者、文档历史记录、跟踪更改等。", "inr_discount_offer_plans_page_banner": "__flag__ 好消息!我们已为印度用户的高级计划提供70% 折扣折扣。 查看下面的最新低价。", "insert": "插入", "insert_column_left": "在左边插入列", diff --git a/services/web/test/frontend/features/project-list/components/inr-banner.test.tsx b/services/web/test/frontend/features/project-list/components/inr-banner.test.tsx new file mode 100644 index 0000000000..8f9cb07a12 --- /dev/null +++ b/services/web/test/frontend/features/project-list/components/inr-banner.test.tsx @@ -0,0 +1,84 @@ +import INRBanner from '@/features/project-list/components/notifications/ads/inr-banner' +import customLocalStorage from '@/infrastructure/local-storage' +import { fireEvent, render, screen } from '@testing-library/react' +import { expect } from 'chai' + +describe('', function () { + beforeEach(function () { + customLocalStorage.clear() + }) + + it('renders correctly', async function () { + render() + await screen.findByRole('dialog') + + await screen.findByText( + '70% off all Overleaf premium plans for users in India' + ) + + await screen.findByText( + 'Get document history, track changes, additional collaborators, and more at Purchasing Power Parity prices.' + ) + + await screen.findByRole('button', { name: 'Maybe later' }) + + await screen.findByRole('button', { name: 'Get discounted plan' }) + }) + + it('dismisses the modal when the "Maybe later" button is clicked', async function () { + render() + await screen.findByRole('dialog') + + fireEvent.click(screen.getByRole('button', { name: 'Maybe later' })) + + expect(screen.queryByRole('dialog')).to.be.null + + const dismissedUntil = customLocalStorage.getItem( + 'has_dismissed_inr_banner_until' + ) + + expect(dismissedUntil).to.not.be.null + + const nowPlus2Days = new Date() + nowPlus2Days.setDate(nowPlus2Days.getDate() + 2) + + // check if dismissal date is around 1 days after the dismissal via "Maybe later" button + expect(new Date(dismissedUntil)).to.be.greaterThan(new Date()) + expect(new Date(dismissedUntil)).to.be.lessThan(nowPlus2Days) + }) + + it('dismisses the modal when close button is clicked', async function () { + render() + await screen.findByRole('dialog') + + fireEvent.click(screen.getByRole('button', { name: /close/i })) + + expect(screen.queryByRole('dialog')).to.be.null + + const dismissedUntil = customLocalStorage.getItem( + 'has_dismissed_inr_banner_until' + ) + + expect(dismissedUntil).to.not.be.null + + const nowPlus29Days = new Date() + nowPlus29Days.setDate(nowPlus29Days.getDate() + 29) + + const nowPlus31Days = new Date() + nowPlus31Days.setDate(nowPlus31Days.getDate() + 31) + + // check if dismissal date is around 30 days after the dismissal via close button + expect(new Date(dismissedUntil)).to.be.greaterThan(nowPlus29Days) + expect(new Date(dismissedUntil)).to.be.lessThan(nowPlus31Days) + }) + + it('hides the modal when user visits while current date is less than local storage date', function () { + const until = new Date() + until.setDate(until.getDate() + 30) // 30 days + customLocalStorage.setItem('has_dismissed_inr_banner_until', until) + + render() + + expect(screen.queryByRole('dialog')).to.be.null + }) +}) diff --git a/services/web/test/unit/src/Project/ProjectListControllerTests.js b/services/web/test/unit/src/Project/ProjectListControllerTests.js index 22b04e1497..3b5ffd87a0 100644 --- a/services/web/test/unit/src/Project/ProjectListControllerTests.js +++ b/services/web/test/unit/src/Project/ProjectListControllerTests.js @@ -126,6 +126,14 @@ describe('ProjectListController', function () { getUserSubscription: sinon.stub().resolves({}), }, } + this.GeoIpLookup = { + promises: { + getCurrencyCode: sinon.stub().resolves({ + countryCode: 'US', + currencyCode: 'USD', + }), + }, + } this.ProjectListController = SandboxedModule.require(MODULE_PATH, { requires: { @@ -155,6 +163,7 @@ describe('ProjectListController', function () { this.UserPrimaryEmailCheckHandler, '../Notifications/NotificationsBuilder': this.NotificationBuilder, '../Subscription/SubscriptionLocator': this.SubscriptionLocator, + '../../infrastructure/GeoIpLookup': this.GeoIpLookup, }, }) @@ -310,6 +319,38 @@ describe('ProjectListController', function () { this.ProjectListController.projectListPage(this.req, this.res) }) + it('should show INR Banner for Indian users with free account', function (done) { + // usersBestSubscription is only available when saas feature is present + this.Features.hasFeature.withArgs('saas').returns(true) + this.SubscriptionViewModelBuilder.promises.getBestSubscription.resolves({ + type: 'free', + }) + this.GeoIpLookup.promises.getCurrencyCode.resolves({ + countryCode: 'IN', + }) + this.res.render = (pageName, opts) => { + expect(opts.showInrGeoBanner).to.be.true + done() + } + this.ProjectListController.projectListPage(this.req, this.res) + }) + + it('should not show INR Banner for Indian users with premium account', function (done) { + // usersBestSubscription is only available when saas feature is present + this.Features.hasFeature.withArgs('saas').returns(true) + this.SubscriptionViewModelBuilder.promises.getBestSubscription.resolves({ + type: 'individual', + }) + this.GeoIpLookup.promises.getCurrencyCode.resolves({ + countryCode: 'IN', + }) + this.res.render = (pageName, opts) => { + expect(opts.showInrGeoBanner).to.be.false + done() + } + this.ProjectListController.projectListPage(this.req, this.res) + }) + describe('With Institution SSO feature', function () { beforeEach(function (done) { this.institutionEmail = 'test@overleaf.com' diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 299abe345f..df2e184538 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -268,6 +268,20 @@ describe('SubscriptionController', function () { this.SubscriptionController.plansPage(this.req, this.res) }) }) + + describe('showInrGeoBanner data', function () { + it('should return true for Indian Users', function (done) { + this.res.render = (page, opts) => { + page.should.equal('subscriptions/plans') + opts.showInrGeoBanner.should.equal(true) + done() + } + this.GeoIpLookup.promises.getCurrencyCode.resolves({ + countryCode: 'IN', + }) + this.SubscriptionController.plansPage(this.req, this.res) + }) + }) }) describe('interstitialPaymentPage', function () { @@ -301,6 +315,20 @@ describe('SubscriptionController', function () { this.SubscriptionController.interstitialPaymentPage(this.req, this.res) }) }) + + describe('showInrGeoBanner data', function () { + it('should return true for Indian users', function (done) { + this.res.render = (page, opts) => { + page.should.equal('subscriptions/interstitial-payment') + opts.showInrGeoBanner.should.equal(true) + done() + } + this.GeoIpLookup.promises.getCurrencyCode.resolves({ + countryCode: 'IN', + }) + this.SubscriptionController.interstitialPaymentPage(this.req, this.res) + }) + }) }) describe('successfulSubscription', function () {