diff --git a/services/web/app/src/Features/Project/ProjectListController.js b/services/web/app/src/Features/Project/ProjectListController.js index c78ce87532..36a5728c22 100644 --- a/services/web/app/src/Features/Project/ProjectListController.js +++ b/services/web/app/src/Features/Project/ProjectListController.js @@ -359,15 +359,32 @@ async function projectListPage(req, res, next) { let showInrGeoBanner = false let showBrlGeoBanner = false + let showLATAMBanner = false let recommendedCurrency if (usersBestSubscription?.type === 'free') { - const { countryCode } = await GeoIpLookup.promises.getCurrencyCode(req.ip) + const latamGeoPricingAssignment = + await SplitTestHandler.promises.getAssignment( + req, + res, + 'geo-pricing-latam-v2' + ) + + const { countryCode, currencyCode } = + await GeoIpLookup.promises.getCurrencyCode(req.ip) if (countryCode === 'IN') { showInrGeoBanner = true } showBrlGeoBanner = countryCode === 'BR' + + showLATAMBanner = + latamGeoPricingAssignment.variant === 'latam' && + ['MX', 'CO', 'CL', 'PE'].includes(countryCode) + // LATAM Banner needs to know which currency to display + if (showLATAMBanner) { + recommendedCurrency = currencyCode + } } let hasIndividualRecurlySubscription = false @@ -426,6 +443,7 @@ async function projectListPage(req, res, next) { showGroupsAndEnterpriseBanner, groupsAndEnterpriseBannerVariant, showWritefullPromoBanner, + showLATAMBanner, recommendedCurrency, showInrGeoBanner, showBrlGeoBanner, diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 5194b5d7dd..263d0b0c89 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -37,10 +37,10 @@ async function plansPage(req, res) { if (GeoIpLookup.isValidCurrencyParam(queryCurrency)) { currency = queryCurrency } - const { recommendedCurrency, countryCode } = await _getRecommendedCurrency( - req, - res - ) + const { recommendedCurrency, countryCode, geoPricingLATAMTestVariant } = + await _getRecommendedCurrency(req, res) + + const latamCountryBannerDetails = await getLatamCountryBannerDetails(req, res) if (recommendedCurrency && currency == null) { currency = recommendedCurrency } @@ -68,6 +68,7 @@ async function plansPage(req, res) { const plansPageViewSegmentation = { currency: recommendedCurrency, countryCode, + 'geo-pricing-latam-v2': geoPricingLATAMTestVariant, } AnalyticsManager.recordEventForSession( @@ -76,6 +77,10 @@ async function plansPage(req, res) { plansPageViewSegmentation ) + const showLATAMBanner = + geoPricingLATAMTestVariant === 'latam' && + ['MX', 'CO', 'CL', 'PE'].includes(countryCode) + res.render('subscriptions/plans', { title: 'plans_and_pricing', currentView, @@ -93,6 +98,8 @@ async function plansPage(req, res) { SubscriptionHelper.generateInitialLocalizedGroupPrice(currency), showInrGeoBanner: countryCode === 'IN', showBrlGeoBanner: countryCode === 'BR', + showLATAMBanner, + latamCountryBannerDetails, }) } @@ -202,10 +209,10 @@ async function userSubscriptionPage(req, res) { async function interstitialPaymentPage(req, res) { const user = SessionManager.getSessionUser(req.session) - const { recommendedCurrency, countryCode } = await _getRecommendedCurrency( - req, - res - ) + const { recommendedCurrency, countryCode, geoPricingLATAMTestVariant } = + await _getRecommendedCurrency(req, res) + + const latamCountryBannerDetails = await getLatamCountryBannerDetails(req, res) const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(user) @@ -217,6 +224,7 @@ async function interstitialPaymentPage(req, res) { const paywallPlansPageViewSegmentation = { currency: recommendedCurrency, countryCode, + 'geo-pricing-latam-v2': geoPricingLATAMTestVariant, } AnalyticsManager.recordEventForSession( req.session, @@ -224,6 +232,10 @@ async function interstitialPaymentPage(req, res) { paywallPlansPageViewSegmentation ) + const showLATAMBanner = + geoPricingLATAMTestVariant === 'latam' && + ['MX', 'CO', 'CL', 'PE'].includes(countryCode) + res.render('subscriptions/interstitial-payment', { title: 'subscribe', itm_content: req.query?.itm_content, @@ -234,6 +246,8 @@ async function interstitialPaymentPage(req, res) { showSkipLink, showInrGeoBanner: countryCode === 'IN', showBrlGeoBanner: countryCode === 'BR', + showLATAMBanner, + latamCountryBannerDetails, }) } } @@ -551,16 +565,65 @@ async function _getRecommendedCurrency(req, res) { const countryCode = currencyLookup.countryCode let recommendedCurrency = currencyLookup.currencyCode - if (['MXN', 'COP', 'CLP', 'PEN'].includes(recommendedCurrency)) { + const assignmentLATAM = await SplitTestHandler.promises.getAssignment( + req, + res, + 'geo-pricing-latam-v2' + ) + + if ( + ['MXN', 'COP', 'CLP', 'PEN'].includes(recommendedCurrency) && + assignmentLATAM?.variant === 'default' + ) { recommendedCurrency = GeoIpLookup.DEFAULT_CURRENCY_CODE } return { recommendedCurrency, countryCode, + geoPricingLATAMTestVariant: assignmentLATAM?.variant, } } +async function getLatamCountryBannerDetails(req, res) { + const userId = SessionManager.getLoggedInUserId(req.session) + let ip = req.ip + if ( + req.query?.ip && + (await AuthorizationManager.promises.isUserSiteAdmin(userId)) + ) { + ip = req.query.ip + } + const currencyLookup = await GeoIpLookup.promises.getCurrencyCode(ip) + const countryCode = currencyLookup.countryCode + const latamCountryBannerDetails = {} + + switch (countryCode) { + case `MX`: + latamCountryBannerDetails.latamCountryFlag = '🇲🇽' + latamCountryBannerDetails.country = 'Mexico' + latamCountryBannerDetails.discount = '25%' + break + case `CO`: + latamCountryBannerDetails.latamCountryFlag = '🇨🇴' + latamCountryBannerDetails.country = 'Colombia' + latamCountryBannerDetails.discount = '60%' + break + case `CL`: + latamCountryBannerDetails.latamCountryFlag = '🇨🇱' + latamCountryBannerDetails.country = 'Chile' + latamCountryBannerDetails.discount = '30%' + break + case `PE`: + latamCountryBannerDetails.latamCountryFlag = '🇵🇪' + latamCountryBannerDetails.country = 'Peru' + latamCountryBannerDetails.discount = '40%' + break + } + + return latamCountryBannerDetails +} + module.exports = { plansPage: expressify(plansPage), userSubscriptionPage: expressify(userSubscriptionPage), diff --git a/services/web/app/views/project/list-react.pug b/services/web/app/views/project/list-react.pug index 72cafc3721..870c7997f3 100644 --- a/services/web/app/views/project/list-react.pug +++ b/services/web/app/views/project/list-react.pug @@ -31,6 +31,7 @@ block append meta meta(name="ol-showInrGeoBanner" data-type="boolean" content=showInrGeoBanner) meta(name="ol-showBrlGeoBanner" data-type="boolean" content=showBrlGeoBanner) meta(name="ol-recommendedCurrency" data-type="string" content=recommendedCurrency) + meta(name="ol-showLATAMBanner" data-type="boolean" content=showLATAMBanner) meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment) meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription) meta(name="ol-newNotificationStyle" data-type="boolean" content=newNotificationStyle) diff --git a/services/web/app/views/subscriptions/interstitial-payment.pug b/services/web/app/views/subscriptions/interstitial-payment.pug index 14070bbfc6..e0fd28f10b 100644 --- a/services/web/app/views/subscriptions/interstitial-payment.pug +++ b/services/web/app/views/subscriptions/interstitial-payment.pug @@ -23,6 +23,9 @@ block content if showBrlGeoBanner div.notification.notification-type-success.text-centered div.notification-content !{translate("brl_discount_offer_plans_page_banner", {flag: '🇧🇷'})} + if showLATAMBanner + div.notification.notification-type-success.text-centered + div.notification-content !{translate("latam_discount_offer_plans_page_banner", {flag: latamCountryBannerDetails.latamCountryFlag, country: latamCountryBannerDetails.country, discount: latamCountryBannerDetails.discount })} .row .col-md-12 diff --git a/services/web/app/views/subscriptions/plans.pug b/services/web/app/views/subscriptions/plans.pug index 2c417a9c1c..6112638cd1 100644 --- a/services/web/app/views/subscriptions/plans.pug +++ b/services/web/app/views/subscriptions/plans.pug @@ -21,6 +21,9 @@ block content if showBrlGeoBanner div.notification.notification-type-success.text-centered div.notification-content !{translate("brl_discount_offer_plans_page_banner", {flag: '🇧🇷'})} + if showLATAMBanner + div.notification.notification-type-success.text-centered + div.notification-content !{translate("latam_discount_offer_plans_page_banner", {flag: latamCountryBannerDetails.latamCountryFlag, country: latamCountryBannerDetails.country, discount: latamCountryBannerDetails.discount })} .row .col-md-12 diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 5d10739ba3..b608a97249 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -107,8 +107,6 @@ "binary_history_error": "", "blank_project": "", "blocked_filename": "", - "brl_discount_modal_info": "", - "brl_discount_modal_title": "", "browser": "", "bulk_accept_confirm": "", "bulk_reject_confirm": "", @@ -655,6 +653,8 @@ "last_resort_trouble_shooting_guide": "", "last_updated_date_by_x": "", "last_used": "", + "latam_discount_modal_info": "", + "latam_discount_modal_title": "", "latex_places_figures_according_to_a_special_algorithm": "", "latex_places_tables_according_to_a_special_algorithm": "", "layout": "", diff --git a/services/web/frontend/js/features/project-list/components/notifications/ads/brl-banner.tsx b/services/web/frontend/js/features/project-list/components/notifications/ads/brl-banner.tsx index b52d2d66f5..9f5bbc49a1 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/ads/brl-banner.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/ads/brl-banner.tsx @@ -85,19 +85,24 @@ export default function BRLBanner() { return ( - {t('brl_discount_modal_title')} + {t('latam_discount_modal_title')}

{t('brl_discount_modal_title')}

-

{t('brl_discount_modal_info')}

+

+ {t('latam_discount_modal_info', { + discount: '50%', + currencyName: 'Brazilian Reais', + })} +

+ + +
+ ) +} diff --git a/services/web/frontend/js/features/project-list/components/notifications/geo-banners.tsx b/services/web/frontend/js/features/project-list/components/notifications/geo-banners.tsx new file mode 100644 index 0000000000..75627895dd --- /dev/null +++ b/services/web/frontend/js/features/project-list/components/notifications/geo-banners.tsx @@ -0,0 +1,19 @@ +import getMeta from '@/utils/meta' +import BRLBanner from './ads/brl-banner' +import INRBanner from './ads/inr-banner' +import LATAMBanner from './ads/latam-banner' + +function GeoBanners() { + const showInrGeoBanner = getMeta('ol-showInrGeoBanner', false) + const showBrlGeoBanner = getMeta('ol-showBrlGeoBanner', false) + const showLATAMBanner = getMeta('ol-showLATAMBanner', false) + return ( + <> + {showBrlGeoBanner && } + {showLATAMBanner && } + {showInrGeoBanner && } + + ) +} + +export default GeoBanners 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 d8ea25250e..7190a932b5 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 @@ -6,13 +6,12 @@ import ReconfirmationInfo from './groups/affiliation/reconfirmation-info' import GroupsAndEnterpriseBanner from './groups-and-enterprise-banner' import WritefullPremiumPromoBanner from './writefull-premium-promo-banner' import GroupSsoSetupSuccess from './groups/group-sso-setup-success' -import INRBanner from './ads/inr-banner' import getMeta from '../../../../utils/meta' import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' import customLocalStorage from '../../../../infrastructure/local-storage' import { sendMB } from '../../../../infrastructure/event-tracking' import classNames from 'classnames' -import BRLBanner from './ads/brl-banner' +import GeoBanners from './geo-banners' type Subscription = { groupId: string @@ -36,8 +35,6 @@ function UserNotifications() { 'ol-groupSubscriptionsPendingEnrollment', [] ) - const showInrGeoBanner = getMeta('ol-showInrGeoBanner', false) - const showBrlGeoBanner = getMeta('ol-showBrlGeoBanner', false) const user = getMeta('ol-user') // Temporary workaround to prevent also showing groups/enterprise banner @@ -85,8 +82,8 @@ function UserNotifications() { + {!showWritefull && !dismissedWritefull && } - {showInrGeoBanner && } - {showBrlGeoBanner && } ) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 2bf9b43a8e..3b3f6992ea 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -176,8 +176,6 @@ "bonus_please_recommend_us": "Bonus - Please recommend us", "bonus_share_link_text": "Online LaTeX Editor __appName__", "bonus_twitter_share_text": "I’m using __appName__, the free online collaborative LaTeX editor - it’s awesome and easy to use!", - "brl_discount_modal_info": "Unlock the full potential of Overleaf with a 50% discount on premium subscriptions paid in Brazilian Reais. Get a longer compile timeout, full document history, track changes, additional collaborators, and more.", - "brl_discount_modal_title": "Premium subscription discount", "brl_discount_offer_plans_page_banner": "__flag__ Great news! We’ve applied a 50% discount to premium plans on this page for our users in Brazil. Check out the new lower prices.", "browser": "Browser", "built_in": "Built-In", @@ -972,6 +970,9 @@ "last_updated": "Last Updated", "last_updated_date_by_x": "__lastUpdatedDate__ by __person__", "last_used": "last used", + "latam_discount_modal_info": "Unlock the full potential of Overleaf with a __discount__% discount on premium subscriptions paid in __currencyName__. Get a longer compile timeout, full document history, track changes, additional collaborators, and more.", + "latam_discount_modal_title": "Premium subscription discount", + "latam_discount_offer_plans_page_banner": "__flag__ Great news! We’ve applied a __discount__ discount to premium plans on this page for our users in __country__. Check out the new lower prices.", "latex_articles_page_summary": "Papers, presentations, reports and more, written in LaTeX and published by our community. Search or browse below.", "latex_articles_page_title": "Articles - Papers, Presentations, Reports and more", "latex_editor_info": "Everything you need in a modern LaTeX editor --- spell check, intelligent autocomplete, syntax highlighting, dozens of color themes, vim and emacs bindings, help with LaTeX warnings and error messages, and much more.", diff --git a/services/web/locales/pt.json b/services/web/locales/pt.json index f734cebfd4..95f6f5145a 100644 --- a/services/web/locales/pt.json +++ b/services/web/locales/pt.json @@ -85,8 +85,6 @@ "bonus_please_recommend_us": "Bônus - Por favor nos recomende", "bonus_share_link_text": "Editor Online de LaTeX __appName__", "bonus_twitter_share_text": "Estou usando o __appName__, o editor gratuito online e colaborativo de LaTeX - é muito bom e fácil de usar!", - "brl_discount_modal_info": "Obtenha todo o potencial do Overleaf com desconto de 50% em assinaturas premium pagas em reais. Obtenha um tempo limite de compilação mais longo, histórico completo de documentos, controle de alterações, colaboradores adicionais e muito mais.", - "brl_discount_modal_title": "Desconto em assinaturas premium", "brl_discount_offer_plans_page_banner": "__flag__ Aplicamos um desconto de 50% aos planos premium nesta página para nossos usuários no Brasil. Confira os novos preços mais baixos.", "built_in": "Embutido", "bulk_accept_confirm": "Vocês tem certeza que deseja aceitar as __nChanges__ alterações selecionadas?", @@ -327,6 +325,8 @@ "language": "Idioma", "last_modified": "Última Modificação", "last_name": "Sobrenome", + "latam_discount_modal_info": "Obtenha todo o potencial do Overleaf com desconto de __discount__% em assinaturas premium pagas em __currencyName__. Obtenha um tempo limite de compilação mais longo, histórico completo de documentos, controle de alterações, colaboradores adicionais e muito mais.", + "latam_discount_modal_title": "Desconto em assinaturas premium", "latex_editor_info": "Tudo que você precisa num editar moderno de LaTeX - correção ortográfica, auto-completar inteligente, realce de sintaxe, diversas cores de temas, atalhos de vim e emacs, ajuda com mensagens de aviso e erros do LaTeX e muito mais.", "latex_templates": "Modelos LaTeX", "ldap": "LDAP", diff --git a/services/web/public/img/subscriptions/chile-discount-modal.png b/services/web/public/img/subscriptions/chile-discount-modal.png new file mode 100644 index 0000000000..49d5e7fd27 Binary files /dev/null and b/services/web/public/img/subscriptions/chile-discount-modal.png differ diff --git a/services/web/public/img/subscriptions/colombia-discount-modal.png b/services/web/public/img/subscriptions/colombia-discount-modal.png new file mode 100644 index 0000000000..6803a4617c Binary files /dev/null and b/services/web/public/img/subscriptions/colombia-discount-modal.png differ diff --git a/services/web/public/img/subscriptions/mexico-discount-modal.png b/services/web/public/img/subscriptions/mexico-discount-modal.png new file mode 100644 index 0000000000..eacefbf102 Binary files /dev/null and b/services/web/public/img/subscriptions/mexico-discount-modal.png differ diff --git a/services/web/public/img/subscriptions/peru-discount-modal.png b/services/web/public/img/subscriptions/peru-discount-modal.png new file mode 100644 index 0000000000..1a9b935422 Binary files /dev/null and b/services/web/public/img/subscriptions/peru-discount-modal.png differ