[web] Paywall CTA split-test (#17555)

* Fix `{splitTest.requiredCohortSize && (...)}` can display `0`

* Get `paywall-cta` assignment in ProjectController.js

* CTA change: "Get Dropbox Sync"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578763286026

* CTA change: "Get GitHub Sync"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578763286736

* CTA change: "Get Git integration"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578763286283

* CTA change: "Add more collaborators"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578763884855

* CTA change: "Get track changes"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764030761

* Update wording and position: "Already subscribed? Try refreshing the page."

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764366969

* Casing update: "Upgrade to track changes"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764209961

* CTA changes: "Get Mendeley integration" + "Get Zotero integration"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764547424

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764547269

* CTA change: "Get full project history"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764762005

* Casing update: "full project history"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764762280

* CTA change: "Get more compile time" in timeout-upgrade-prompt-new.tsx

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764762563

* CTA change: "Get more compile time" in compile-timeout-warning.tsx

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764762726

* CTA change: "Get advanced reference search"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764969087

* Update casing and wording: "advanced reference search"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764969412

* CTA change: "Get Symbol Palette"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578765128906

* Update title: "Subscribe to find the symbols you need faster"

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578765289664

* Revert use of `satisfies`: it doesn't work with our version of prettier (https://github.com/prettier/prettier/issues/13516)

* CTA change: "Get more compile time" in compile-timeout-changing-soon.tsx

⚠️ not in miro

* Rename `paywallCtaAssignment`, remove useless export and comment

Addressing Fahru's review

* Add alternative texts in `/registration/try-premium` page

* CTA change: "Get more compile time" in timeout-upgrade-prompt.jsx

https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578764762563

* CTA change: "Get GitHub Sync" in import-project-from-github-modal-content.tsx

Not in Miro, but related to: https://miro.com/app/board/uXjVMFLu5J8=/?openComment=3458764578763286736

* CTA change: "Get more compile time" in compile-time-warning.tsx

Not in Miro, but related to others

* Fix compile-time-warning style (spacings, overflows out of window)

* `npm run format:fix`

GitOrigin-RevId: 0d8d1808b5901c2761d35494c49713d26721bcfd
This commit is contained in:
Antoine Clausse 2024-04-02 16:06:16 +02:00 committed by Copybot
parent 6632ffc939
commit 92a58c8f3b
14 changed files with 113 additions and 18 deletions

View file

@ -616,6 +616,9 @@ const ProjectController = {
compileLogEventsAssignment(cb) {
SplitTestHandler.getAssignment(req, res, 'compile-log-events', cb)
},
paywallCtaAssignment(cb) {
SplitTestHandler.getAssignment(req, res, 'paywall-cta', cb)
},
projectTags(cb) {
if (!userId) {
return cb(null, [])

View file

@ -400,6 +400,15 @@ async function projectListPage(req, res, next) {
)
}
try {
await SplitTestHandler.promises.getAssignment(req, res, 'paywall-cta')
} catch (error) {
logger.error(
{ err: error },
'failed to get "paywall-cta" split test assignment'
)
}
res.render('project/list-react', {
title: 'your_projects',
usersBestSubscription,

View file

@ -41,6 +41,7 @@
"add_company_details": "",
"add_email_to_claim_features": "",
"add_files": "",
"add_more_collaborators": "",
"add_more_managers": "",
"add_more_members": "",
"add_new_email": "",
@ -65,6 +66,7 @@
"all_premium_features_including": "",
"all_projects": "",
"all_projects_will_be_transferred_immediately": "",
"already_subscribed_try_refreshing_the_page": "",
"also": "",
"an_email_has_already_been_sent_to": "",
"an_error_occurred_when_verifying_the_coupon_code": "",
@ -409,7 +411,6 @@
"find_out_more_about_institution_login": "",
"find_out_more_about_the_file_outline": "",
"find_out_more_nt": "",
"find_the_symbols_you_need_with_premium": "",
"first_name": "",
"first_x_days_free_after_that_y_per_month": "",
"first_x_days_free_after_that_y_per_year": "",
@ -439,9 +440,17 @@
"generic_if_problem_continues_contact_us": "",
"generic_linked_file_compile_error": "",
"generic_something_went_wrong": "",
"get_advanced_reference_search": "",
"get_collaborative_benefits": "",
"get_discounted_plan": "",
"get_dropbox_sync": "",
"get_full_project_history": "",
"get_git_integration": "",
"get_github_sync": "",
"get_more_compile_time": "",
"get_most_subscription_by_checking_features": "",
"get_symbol_palette": "",
"get_track_changes": "",
"git": "",
"git_authentication_token": "",
"git_authentication_token_create_modal_info_1": "",
@ -741,6 +750,7 @@
"maximum_files_uploaded_together": "",
"maybe_later": "",
"members_management": "",
"mendeley_cta": "",
"mendeley_groups_loading_error": "",
"mendeley_groups_relink": "",
"mendeley_integration": "",
@ -1247,6 +1257,7 @@
"subject_to_additional_vat": "",
"submit_title": "",
"subscribe": "",
"subscribe_to_find_the_symbols_you_need_faster": "",
"subscription_admins_cannot_be_deleted": "",
"subscription_canceled": "",
"subscription_canceled_and_terminate_on_x": "",
@ -1594,6 +1605,7 @@
"youve_unlinked_all_users": "",
"zoom_in": "",
"zoom_out": "",
"zotero_cta": "",
"zotero_groups_loading_error": "",
"zotero_groups_relink": "",
"zotero_integration": "",

View file

@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from 'react'
import * as eventTracking from '../../../../infrastructure/event-tracking'
import StartFreeTrialButton from '../../../../shared/components/start-free-trial-button'
import { paywallPrompt } from '../../../../main/account-upgrade'
import { useSplitTestContext } from '@/shared/context/split-test-context'
function FeatureItem({ text }: { text: string }) {
return (
@ -17,6 +18,9 @@ export function OwnerPaywallPrompt() {
const { t } = useTranslation()
const [clickedFreeTrialButton, setClickedFreeTrialButton] = useState(false)
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
useEffect(() => {
eventTracking.send('subscription-funnel', 'editor-click-feature', 'history')
paywallPrompt('history')
@ -32,7 +36,7 @@ export function OwnerPaywallPrompt() {
<p>{t('currently_seeing_only_24_hrs_history')}</p>
<p>
<strong>
{t('upgrade_to_get_feature', { feature: 'full Project History' })}
{t('upgrade_to_get_feature', { feature: 'full project history' })}
</strong>
</p>
<ul className="history-feature-list">
@ -50,7 +54,11 @@ export function OwnerPaywallPrompt() {
source="history"
buttonProps={{ bsStyle: 'default', className: 'btn-premium' }}
handleClick={handleFreeTrialClick}
/>
>
{hasNewPaywallCta
? t('get_full_project_history')
: t('start_free_trial')}
</StartFreeTrialButton>
</p>
{clickedFreeTrialButton ? (
<p className="small">{t('refresh_page_after_starting_free_trial')}</p>

View file

@ -5,6 +5,7 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import { useSplitTestContext } from '@/shared/context/split-test-context'
const TWENTY_FOUR_DAYS = 24 * 60 * 60 * 24 * 1000
@ -24,6 +25,9 @@ function CompileTimeWarning() {
isProjectOwner,
} = useDetachCompileContext()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
useEffect(() => {
if (deliveryLatencies && deliveryLatencies.compileTimeServerE2E) {
// compile-timeout-20s test
@ -101,7 +105,7 @@ function CompileTimeWarning() {
handleClick={handleUpgradeClick}
source="compile-time-warning"
>
{t('upgrade')}
{hasNewPaywallCta ? t('get_more_compile_time') : t('upgrade')}
</StartFreeTrialButton>
</div>
</div>

View file

@ -3,6 +3,7 @@ import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
import { Trans, useTranslation } from 'react-i18next'
import * as eventTracking from '@/infrastructure/event-tracking'
import { FC } from 'react'
import { useSplitTestContext } from '@/shared/context/split-test-context'
const sendInfoClickEvent = () => {
eventTracking.sendMB('paywall-info-click', {
@ -17,6 +18,9 @@ export const CompileTimeoutChangingSoon: FC<{
}> = ({ isProjectOwner = false, handleDismissChangingSoon }) => {
const { t } = useTranslation()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
const compileTimeoutChangesBlogLink = (
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
<a
@ -51,7 +55,9 @@ export const CompileTimeoutChangingSoon: FC<{
className: 'btn-secondary-compile-timeout-override',
}}
>
{t('start_free_trial_without_exclamation')}
{hasNewPaywallCta
? t('get_more_compile_time')
: t('start_free_trial_without_exclamation')}
</StartFreeTrialButton>
}
ariaLive="polite"

View file

@ -2,6 +2,7 @@ import Notification from '@/shared/components/notification'
import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
import { useTranslation } from 'react-i18next'
import { FC } from 'react'
import { useSplitTestContext } from '@/shared/context/split-test-context'
export const CompileTimeoutWarning: FC<{
handleDismissWarning: () => void
@ -9,6 +10,9 @@ export const CompileTimeoutWarning: FC<{
}> = ({ handleDismissWarning, showNewCompileTimeoutUI }) => {
const { t } = useTranslation()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
return (
<Notification
action={
@ -19,7 +23,9 @@ export const CompileTimeoutWarning: FC<{
className: 'btn-secondary-compile-timeout-override',
}}
>
{t('start_free_trial_without_exclamation')}
{hasNewPaywallCta
? t('get_more_compile_time')
: t('start_free_trial_without_exclamation')}
</StartFreeTrialButton>
}
ariaLive="polite"

View file

@ -6,6 +6,7 @@ import PdfLogEntry from './pdf-log-entry'
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
import { Button } from 'react-bootstrap'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { useSplitTestContext } from '@/shared/context/split-test-context'
function TimeoutUpgradePromptNew() {
const {
@ -58,6 +59,10 @@ const CompileTimeout = memo(function CompileTimeout({
isProjectOwner,
}: CompileTimeoutProps) {
const { t } = useTranslation()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
return (
<PdfLogEntry
headerTitle={t('your_compile_timed_out')}
@ -107,7 +112,9 @@ const CompileTimeout = memo(function CompileTimeout({
block: true,
}}
>
{t('start_a_free_trial')}
{hasNewPaywallCta
? t('get_more_compile_time')
: t('start_a_free_trial')}
</StartFreeTrialButton>
</p>
)}

View file

@ -4,12 +4,16 @@ import StartFreeTrialButton from '../../../shared/components/start-free-trial-bu
import { memo } from 'react'
import PdfLogEntry from './pdf-log-entry'
import UpgradeBenefits from '../../../shared/components/upgrade-benefits'
import { useSplitTestContext } from '@/shared/context/split-test-context'
function TimeoutUpgradePrompt() {
const { t } = useTranslation()
const { hasPremiumCompile, isProjectOwner } = useEditorContext()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
if (!window.ExposedSettings.enableSubscriptions || hasPremiumCompile) {
return null
}
@ -36,7 +40,11 @@ function TimeoutUpgradePrompt() {
bsStyle: 'success',
className: 'row-spaced-small',
}}
/>
>
{hasNewPaywallCta
? t('get_more_compile_time')
: t('start_free_trial')}
</StartFreeTrialButton>
</p>
)}
</>

View file

@ -25,6 +25,7 @@ import LoadMore from './load-more'
import { useEffect } from 'react'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import { GenericErrorBoundaryFallback } from '../../../shared/components/generic-error-boundary-fallback'
import { SplitTestProvider } from '@/shared/context/split-test-context'
function ProjectListRoot() {
const { isReady } = useWaitForI18n()
@ -40,7 +41,9 @@ export function ProjectListRootInner() {
return (
<ProjectListProvider>
<ColorPickerProvider>
<SplitTestProvider>
<ProjectListPageContent />
</SplitTestProvider>
</ColorPickerProvider>
</ProjectListProvider>
)

View file

@ -18,6 +18,7 @@ export default function AddCollaboratorsUpgrade() {
const { splitTestVariants } = useSplitTestContext()
const variant = splitTestVariants['project-share-modal-paywall']
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
return (
<div className={variant === 'default' ? 'add-collaborators-upgrade' : ''}>
@ -33,7 +34,11 @@ export default function AddCollaboratorsUpgrade() {
buttonProps={{ bsStyle: 'success' }}
handleClick={() => setStartedFreeTrial(true)}
source="project-sharing"
/>
>
{hasNewPaywallCta
? t('add_more_collaborators')
: t('start_free_trial')}
</StartFreeTrialButton>
) : (
<Button
bsStyle="primary"

View file

@ -6,6 +6,7 @@ import { useProjectContext } from '../../../../shared/context/project-context'
import { useUserContext } from '../../../../shared/context/user-context'
import { startFreeTrial, upgradePlan } from '../../../../main/account-upgrade'
import { memo } from 'react'
import { useSplitTestContext } from '@/shared/context/split-test-context'
type UpgradeTrackChangesModalProps = {
show: boolean
@ -20,6 +21,9 @@ function UpgradeTrackChangesModal({
const project = useProjectContext()
const user = useUserContext()
const { splitTestVariants } = useSplitTestContext()
const hasNewPaywallCta = splitTestVariants['paywall-cta'] === 'enabled'
return (
<AccessibleModal show={show} onHide={() => setShow(false)}>
<Modal.Header closeButton>
@ -42,7 +46,6 @@ function UpgradeTrackChangesModal({
<h4 className="teaser-title">
{t('see_changes_in_your_documents_live')}
</h4>
<p className="small">{t('refresh_page_after_starting_free_trial')}</p>
<Row>
<Col md={10} mdOffset={1}>
<ul className="list-unstyled">
@ -58,6 +61,9 @@ function UpgradeTrackChangesModal({
</ul>
</Col>
</Row>
<p className="small">
{t('already_subscribed_try_refreshing_the_page')}
</p>
{project.owner && (
<Row className="text-center">
{project.owner._id === user.id ? (
@ -67,7 +73,9 @@ function UpgradeTrackChangesModal({
className="btn-primary"
onClick={() => startFreeTrial('track-changes')}
>
{t('try_it_for_free')}
{hasNewPaywallCta
? t('get_track_changes')
: t('try_it_for_free')}
</Button>
) : (
<Button

View file

@ -654,24 +654,24 @@ CodeMirror
.compile-time-warning {
padding: @padding-sm;
background-color: @ol-green;
width: 420px;
max-width: 420px;
box-shadow: 5px 5px 6px rgba(0, 0, 0, 0.3);
.warning-content {
display: flex;
align-items: center;
justify-content: space-between;
margin-right: 32px;
flex-wrap: wrap;
gap: @alert-padding;
}
.warning-text {
max-width: 300px;
padding-right: @alert-padding;
display: inline-block;
font-size: @font-size-small;
}
.upgrade-prompt {
display: inline-flex;
margin-left: auto;
}
button.btn {

View file

@ -62,6 +62,7 @@
"add_email": "Add Email",
"add_email_to_claim_features": "Add an institutional email address to claim your features.",
"add_files": "Add Files",
"add_more_collaborators": "Add more collaborators",
"add_more_managers": "Add more managers",
"add_more_members": "Add more members",
"add_new_email": "Add new email",
@ -97,6 +98,7 @@
"all_projects_will_be_transferred_immediately": "All projects will be transferred to the new owner immediately.",
"all_templates": "All Templates",
"already_have_sl_account": "Already have an __appName__ account?",
"already_subscribed_try_refreshing_the_page": "Already subscribed? Try refreshing the page.",
"also": "Also",
"also_available_as_on_premises": "Also available as On-Premises",
"alternatively_create_new_institution_account": "Alternatively, you can create a <b>new account</b> with your institution email (<b>__email__</b>) by clicking <b>__clickText__</b>.",
@ -608,7 +610,6 @@
"find_out_more_about_institution_login": "Find out more about institutional login",
"find_out_more_about_the_file_outline": "Find out more about the file outline",
"find_out_more_nt": "Find out more.",
"find_the_symbols_you_need_with_premium": "Find the symbols you need faster with Overleaf Premium",
"first_name": "First Name",
"first_x_days_free_after_that_y_per_month": "First <0>__trialLen__ days free</0>, after that <0>__price__</0> per month",
"first_x_days_free_after_that_y_per_year": "First <0>__trialLen__ days free</0>, after that <0>__price__</0> per year",
@ -670,12 +671,21 @@
"generic_if_problem_continues_contact_us": "If the problem continues please contact us",
"generic_linked_file_compile_error": "This projects output files are not available because it failed to compile. Please open the project to see the compilation error details.",
"generic_something_went_wrong": "Sorry, something went wrong",
"get_advanced_reference_search": "Get advanced reference search",
"get_collaborative_benefits": "Get the collaborative benefits from __appName__, even if you prefer to work offline",
"get_discounted_plan": "Get discounted plan",
"get_dropbox_sync": "Get Dropbox Sync",
"get_full_project_history": "Get full project history",
"get_git_integration": "Get Git integration",
"get_github_sync": "Get GitHub Sync",
"get_in_touch_having_problems": "<a href=\"__link__\">Get in touch with support</a> if youre having problems",
"get_involved": "Get involved",
"get_more_compile_time": "Get more compile time",
"get_most_subscription_by_checking_features": "Get the most out of your __appName__ subscription by checking out <0>__appName__s features</0>.",
"get_symbol_palette": "Get Symbol Palette",
"get_the_best_writing_experience": "Get the best writing experience",
"get_the_most_out_headline": "Get the most out of __appName__ with features such as:",
"get_track_changes": "Get track changes",
"git": "Git",
"git_authentication_token": "Git authentication token",
"git_authentication_token_create_modal_info_1": "This is your Git authentication token. You should enter this when prompted for a password.",
@ -1107,6 +1117,7 @@
"maybe_later": "Maybe later",
"members_management": "Members management",
"mendeley": "Mendeley",
"mendeley_cta": "Get Mendeley integration",
"mendeley_groups_loading_error": "There was an error loading groups from Mendeley",
"mendeley_groups_relink": "There was an error accessing your Mendeley data. This was likely caused by lack of permissions. Please re-link your account and try again.",
"mendeley_integration": "Mendeley Integration",
@ -1185,6 +1196,7 @@
"no_existing_password": "Please use the password reset form to set your password",
"no_featured_templates": "No featured templates",
"no_folder": "No folder",
"no_i_dont_need_these": "No, I dont need these",
"no_image_files_found": "No image files found",
"no_members": "No members",
"no_messages": "No messages",
@ -1806,6 +1818,7 @@
"submit": "submit",
"submit_title": "Submit",
"subscribe": "Subscribe",
"subscribe_to_find_the_symbols_you_need_faster": "Subscribe to find the symbols you need faster",
"subscription": "Subscription",
"subscription_admin_panel": "admin panel",
"subscription_admins_cannot_be_deleted": "You cannot delete your account while on a subscription. Please cancel your subscription and try again. If you keep seeing this message please contact us.",
@ -2071,7 +2084,7 @@
"upgrade_for_plenty_more_compile_time": "Upgrade to get plenty more compile time.",
"upgrade_now": "Upgrade Now",
"upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
"upgrade_to_track_changes": "Upgrade to Track Changes",
"upgrade_to_track_changes": "Upgrade to track changes",
"upload": "Upload",
"upload_failed": "Upload failed",
"upload_from_computer": "Upload from computer",
@ -2155,6 +2168,7 @@
"work_or_university_sso": "Work/university single sign-on",
"work_with_non_overleaf_users": "Work with non Overleaf users",
"would_you_like_to_see_a_university_subscription": "Would you like to see a university-wide __appName__ subscription at your university?",
"write_and_collaborate_faster_with_features_like": "Write and collaborate faster with features like:",
"writefull": "Writefull",
"writefull_alternate_login_prompt": "If you have a Writefull account, <0>log in</0> to get started. If you dont have a Writefull account, well create one for you. By enabling Writefull, you accept Writefulls <1>terms of service</1> and <2>privacy policy.</2>",
"writefull_disable_prompt_body": "Writefull is enabled. You can switch it on or off in the account settings anytime.",
@ -2175,6 +2189,7 @@
"x_price_per_user": "__price__ per user",
"x_price_per_year": "__price__ per year",
"year": "year",
"yes_im_in": "Yes, Im in",
"yes_move_me_to_personal_plan": "Yes, move me to the Personal plan",
"yes_that_is_correct": "Yes, thats correct",
"you": "You",
@ -2255,6 +2270,7 @@
"zoom_out": "Zoom out",
"zotero": "Zotero",
"zotero_and_mendeley_integrations": "<0>Zotero</0> and <0>Mendeley</0> integrations",
"zotero_cta": "Get Zotero integration",
"zotero_groups_loading_error": "There was an error loading groups from Zotero",
"zotero_groups_relink": "There was an error accessing your Zotero data. This was likely caused by lack of permissions. Please re-link your account and try again.",
"zotero_integration": "Zotero Integration",