mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
Merge pull request #17230 from overleaf/mf-tear-down-inr-2
[web] Tear down INR split test GitOrigin-RevId: 90c6ab4666d9281e804b279d697d6c14ffd682d2
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
+48
-120
@@ -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 (
|
||||
<Notification
|
||||
bsStyle="info"
|
||||
onDismiss={bannerDismissed}
|
||||
body={
|
||||
<Trans
|
||||
i18nKey="inr_discount_offer"
|
||||
components={[<b />]} // eslint-disable-line react/jsx-key
|
||||
return (
|
||||
<AccessibleModal show={showModal} onHide={handleHide} backdrop="static">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('inr_discount_modal_title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="modal-body-share">
|
||||
<p>
|
||||
<img
|
||||
alt={t('inr_discount_modal_title')}
|
||||
src="/img/subscriptions/inr-discount-modal.png"
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
}
|
||||
action={
|
||||
<Button
|
||||
bsStyle={newNotificationStyle ? null : 'info'}
|
||||
bsSize="sm"
|
||||
className={newNotificationStyle ? 'btn-secondary' : 'pull-right'}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t('get_discounted_plan')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else if (variant === 'green-banner') {
|
||||
return (
|
||||
<Notification
|
||||
bsStyle="success"
|
||||
onDismiss={bannerDismissed}
|
||||
body={
|
||||
<Trans
|
||||
i18nKey="inr_discount_offer_green_banner"
|
||||
components={[<b />, <br />]} // eslint-disable-line react/jsx-key
|
||||
values={{ flag: '🇮🇳' }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
}
|
||||
action={
|
||||
<Button
|
||||
bsStyle={newNotificationStyle ? null : 'success'}
|
||||
bsSize="sm"
|
||||
className={newNotificationStyle ? 'btn-secondary' : 'pull-right'}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t('get_discounted_plan')} ₹
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else if (variant === 'modal') {
|
||||
return (
|
||||
<AccessibleModal show={showModal} onHide={handleHide}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('inr_discount_modal_title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="modal-body-share">
|
||||
<p>
|
||||
<img
|
||||
alt={t('inr_discount_modal_title')}
|
||||
src="/img/subscriptions/inr-discount-modal.png"
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>{t('inr_discount_modal_info')}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button bsStyle="default" onClick={handleMaybeLater}>
|
||||
{t('maybe_later')}
|
||||
</Button>
|
||||
<Button type="button" bsStyle="primary" onClick={handleClick}>
|
||||
{t('get_discounted_plan')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
</p>
|
||||
<p>{t('inr_discount_modal_info')}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button bsStyle="default" onClick={handleMaybeLater}>
|
||||
{t('maybe_later')}
|
||||
</Button>
|
||||
<Button type="button" bsStyle="primary" onClick={handleClick}>
|
||||
{t('get_discounted_plan')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-14
@@ -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() {
|
||||
<Institution />
|
||||
<ConfirmEmail />
|
||||
<ReconfirmationInfo />
|
||||
{!showInrGeoBanner && !showWritefull && !dismissedWritefull && (
|
||||
<GroupsAndEnterpriseBanner />
|
||||
)}
|
||||
{showInrGeoBanner && (
|
||||
<INRBanner
|
||||
variant={inrGeoBannerVariant}
|
||||
splitTestName={inrGeoBannerSplitTestName}
|
||||
/>
|
||||
)}
|
||||
{!showWritefull && !dismissedWritefull && <GroupsAndEnterpriseBanner />}
|
||||
{showInrGeoBanner && <INRBanner />}
|
||||
{writefullIntegrationSplitTestEnabled || user?.writefull?.enabled ? (
|
||||
<WritefullPremiumPromoBanner
|
||||
show={showWritefull}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import INRBanner from '@/features/project-list/components/notifications/ads/inr-banner'
|
||||
|
||||
export const Default = () => {
|
||||
return <INRBanner />
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Project List / INR Banner',
|
||||
component: INRBanner,
|
||||
}
|
||||
@@ -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</0> 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",
|
||||
|
||||
@@ -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</0> 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",
|
||||
|
||||
@@ -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</0> on our premium features.",
|
||||
"inr_discount_offer_green_banner": "__flag__ <0>Big news!</0> You qualify for a <0>70% discount</0> 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__ <b>Great news!</b> We’ve applied a <b>70% discount</b> discount to premium plans for our users in India. Check out the new lower prices below.",
|
||||
"insert": "Insert",
|
||||
"insert_column_left": "Insert column left",
|
||||
|
||||
@@ -855,8 +855,6 @@
|
||||
"info": "信息",
|
||||
"inr_discount_modal_info": "以平价获取文档历史记录、跟踪更改、更多协作者等功能。",
|
||||
"inr_discount_modal_title": "面向印度用户的所有 Overleaf 高级计划七折优惠",
|
||||
"inr_discount_offer": "好消息! 您现在可以使用卢比 ₹ 来支付 Overleaf 订阅费用,从而在使用我们的高级功能时享受<0>70% 的折扣</0>。",
|
||||
"inr_discount_offer_green_banner": "__flag__ <0>好消息!</0>由于您在印度,因此您有资格享受我们的高级计划的<0>70% 折扣</0>。 <1/>获取其他协作者、文档历史记录、跟踪更改等。",
|
||||
"inr_discount_offer_plans_page_banner": "__flag__ <b>好消息!</b>我们已为印度用户的高级计划提供<b>70% 折扣</b>折扣。 查看下面的最新低价。",
|
||||
"insert": "插入",
|
||||
"insert_column_left": "在左边插入列",
|
||||
|
||||
@@ -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('<INRBanner />', function () {
|
||||
beforeEach(function () {
|
||||
customLocalStorage.clear()
|
||||
})
|
||||
|
||||
it('renders correctly', async function () {
|
||||
render(<INRBanner />)
|
||||
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(<INRBanner />)
|
||||
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(<INRBanner />)
|
||||
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(<INRBanner />)
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.be.null
|
||||
})
|
||||
})
|
||||
@@ -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'
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user