Merge pull request #16167 from overleaf/em-writefull-promo

Writefull integration 10% promo

GitOrigin-RevId: 27ef5e51d7d9c56d85ccc44692444bff46fbeeec
This commit is contained in:
Eric Mc Sween
2023-12-12 08:02:04 -05:00
committed by Copybot
parent 369d5cb406
commit ee0dbcf331
6 changed files with 282 additions and 24 deletions

View File

@@ -103,7 +103,7 @@ async function projectListPage(req, res, next) {
const user = await User.findById(
userId,
`email emails features alphaProgram betaProgram lastPrimaryEmailCheck signUpDate${
isSaas ? ' enrollment' : ''
isSaas ? ' enrollment writefull' : ''
}`
)
@@ -385,6 +385,19 @@ async function projectListPage(req, res, next) {
}
}
try {
await SplitTestHandler.promises.getAssignment(
req,
res,
'writefull-integration'
)
} catch (err) {
logger.warn(
{ err },
'failed to get "writefull-integration" split test assignment'
)
}
let showInrGeoBanner, inrGeoBannerSplitTestName
let inrGeoBannerVariant = 'default'
let showLATAMBanner = false

View File

@@ -5,6 +5,7 @@ import ConfirmEmail from './groups/confirm-email'
import ReconfirmationInfo from './groups/affiliation/reconfirmation-info'
import GroupsAndEnterpriseBanner from './groups-and-enterprise-banner'
import WritefullPromoBanner from './writefull-promo-banner'
import WritefullPremiumPromoBanner from './writefull-premium-promo-banner'
import GroupSsoSetupSuccess from './groups/group-sso-setup-success'
import INRBanner from './ads/inr-banner'
import LATAMBanner from './ads/latam-banner'
@@ -12,7 +13,9 @@ 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 { isSplitTestEnabled } from '@/utils/splitTestUtils'
const WRITEFULL_PROMO_DELAY_MS = 24 * 60 * 60 * 1000 // 1 day
const isChromium = () =>
(window.navigator as any).userAgentData?.brands?.some(
(item: { brand: string }) => item.brand === 'Chromium'
@@ -44,22 +47,52 @@ function UserNotifications() {
'unassigned'
)
const showLATAMBanner = getMeta('ol-showLATAMBanner', false)
const writefullIntegrationSplitTestEnabled = isSplitTestEnabled(
'writefull-integration'
)
// Temporary workaround to prevent also showing groups/enterprise banner
const [showWritefull, setShowWritefull] = useState(() => {
if (isChromium()) {
const show =
getMeta('ol-showWritefullPromoBanner') &&
!customLocalStorage.getItem('has_dismissed_writefull_promo_banner')
if (show) {
sendMB('promo-prompt', {
location: 'dashboard-banner',
page: '/project',
name: 'writefull',
})
}
return show
const dismissed = customLocalStorage.getItem(
'has_dismissed_writefull_promo_banner'
)
if (dismissed) {
return false
}
let show = false
if (writefullIntegrationSplitTestEnabled) {
// Show the Writefull promo 1 day after it has been enabled
const user = getMeta('ol-user')
if (user.writefull?.enabled) {
const scheduledAt = customLocalStorage.getItem(
'writefull_promo_scheduled_at'
)
if (scheduledAt == null) {
customLocalStorage.setItem(
'writefull_promo_scheduled_at',
new Date(Date.now() + WRITEFULL_PROMO_DELAY_MS).toISOString()
)
} else if (new Date() >= new Date(scheduledAt)) {
show = true
}
}
} else {
// Only show the Writefull extension promo on Chrome browsers
show = isChromium() && getMeta('ol-showWritefullPromoBanner')
}
if (show) {
sendMB('promo-prompt', {
location: 'dashboard-banner',
page: '/project',
name: writefullIntegrationSplitTestEnabled
? 'writefull-premium'
: 'writefull',
})
}
return show
})
const [dismissedWritefull, setDismissedWritefull] = useState(false)
@@ -91,13 +124,23 @@ function UserNotifications() {
splitTestName={inrGeoBannerSplitTestName}
/>
) : null}
<WritefullPromoBanner
show={showWritefull}
setShow={setShowWritefull}
onDismiss={() => {
setDismissedWritefull(true)
}}
/>
{writefullIntegrationSplitTestEnabled ? (
<WritefullPremiumPromoBanner
show={showWritefull}
setShow={setShowWritefull}
onDismiss={() => {
setDismissedWritefull(true)
}}
/>
) : (
<WritefullPromoBanner
show={showWritefull}
setShow={setShowWritefull}
onDismiss={() => {
setDismissedWritefull(true)
}}
/>
)}
</ul>
</div>
)

View File

@@ -0,0 +1,67 @@
import { memo, useCallback } from 'react'
import Notification from '@/shared/components/notification'
import { sendMB } from '@/infrastructure/event-tracking'
import customLocalStorage from '@/infrastructure/local-storage'
import WritefullLogo from '@/shared/svgs/writefull-logo'
const eventSegmentation = {
location: 'dashboard-banner',
page: '/project',
name: 'writefull-premium',
}
function WritefullPremiumPromoBanner({
show,
setShow,
onDismiss,
}: {
show: boolean
setShow: (value: boolean) => void
onDismiss: () => void
}) {
const handleClose = useCallback(() => {
customLocalStorage.setItem(
'has_dismissed_writefull_promo_banner',
new Date()
)
customLocalStorage.removeItem('writefull_promo_scheduled_at')
setShow(false)
sendMB('promo-dismiss', eventSegmentation)
onDismiss()
}, [setShow, onDismiss])
if (!show) {
return null
}
return (
<Notification
type="info"
isDismissible
onDismiss={handleClose}
content={
<>
Enjoying Writefull? Get <strong>10% off Writefull Premium</strong>,
giving you access to TeXGPTAI assistance to generate LaTeX code. Use{' '}
<strong>OVERLEAF10</strong> at the checkout.
</>
}
action={
<a
className="btn btn-secondary"
href="https://my.writefull.com/plans"
target="_blank"
rel="noreferrer"
onClick={() => {
sendMB('promo-click', eventSegmentation)
}}
>
<WritefullLogo width="16" height="16" />{' '}
<span>Get Writefull Premium</span>
</a>
}
/>
)
}
export default memo(WritefullPremiumPromoBanner)

View File

@@ -62,6 +62,7 @@ function WritefullPromoBanner({
height={16}
width={16}
style={{ marginRight: 4 }}
aria-hidden="true"
/>
<span>Get Writefull for Overleaf</span>
</a>

View File

@@ -1,14 +1,14 @@
function WritefullLogo() {
function WritefullLogo({ width = '40', height = '40' }) {
return (
<svg
width="40"
height="40"
width={width}
height={height}
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
style={{ verticalAlign: 'middle' }}
>
<rect width="40" height="40" fill="white" />
<rect
x="1.6665"
y="1.66666"

View File

@@ -26,6 +26,7 @@ import Common from '../../../../../frontend/js/features/project-list/components/
import Institution from '../../../../../frontend/js/features/project-list/components/notifications/groups/institution'
import ConfirmEmail from '../../../../../frontend/js/features/project-list/components/notifications/groups/confirm-email'
import ReconfirmationInfo from '../../../../../frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info'
import UserNotifications from '../../../../../frontend/js/features/project-list/components/notifications/user-notifications'
import { ProjectListProvider } from '../../../../../frontend/js/features/project-list/context/project-list-context'
import {
Notification,
@@ -959,6 +960,139 @@ describe('<UserNotifications />', function () {
})
})
describe('<WritefullPromoBanner>', function () {
beforeEach(function () {
window.metaAttributesCache = window.metaAttributesCache || new Map()
window.metaAttributesCache.set('ol-ExposedSettings', exposedSettings)
window.metaAttributesCache.set('ol-showWritefullPromoBanner', true)
// The older banner is only shown to Chrome users
const navigator = window.navigator as any
navigator.userAgentData = { brands: [{ brand: 'Chromium' }] }
localStorage.clear()
})
afterEach(function () {
window.metaAttributesCache = window.metaAttributesCache || new Map()
})
describe('when writefull-integration split test is not enabled', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-splitTestVariants', {
'writefull-integration': 'default',
})
})
it('shows the older banner', function () {
renderWithinProjectListProvider(UserNotifications)
const ctaLink = screen.getByRole('link', {
name: 'Get Writefull for Overleaf',
})
expect(ctaLink.getAttribute('href')).to.equal(
'https://my.writefull.com/overleaf-invite?code=OVERLEAF10'
)
})
it('dismisses the banner when the close button is clicked', function () {
renderWithinProjectListProvider(UserNotifications)
screen.getByRole('link', { name: /Writefull/ })
const closeButton = screen.getByRole('button', { name: 'Close' })
fireEvent.click(closeButton)
expect(screen.queryByRole('link', { name: /Writefull/ })).to.be.null
expect(localStorage.getItem('has_dismissed_writefull_promo_banner')).to
.exist
})
it("doesn't show the banner if it has been dismissed", function () {
localStorage.setItem(
'has_dismissed_writefull_promo_banner',
new Date(Date.now() - 1000)
)
renderWithinProjectListProvider(UserNotifications)
expect(screen.queryByRole('link', { name: /Writefull/ })).to.be.null
})
})
describe('when writefull-integration split test is enabled', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-splitTestVariants', {
'writefull-integration': 'enabled',
})
})
describe('when the writefull integration is enabled', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-user', {
writefull: { enabled: true },
})
})
it('schedules the notification for the next day', function () {
renderWithinProjectListProvider(UserNotifications)
expect(localStorage.getItem('writefull_promo_scheduled_at')).to.exist
expect(screen.queryByRole('link', { name: /Writefull/ })).to.be.null
})
it('shows the banner after it has been scheduled', function () {
localStorage.setItem(
'writefull_promo_scheduled_at',
new Date(Date.now() - 1000)
)
renderWithinProjectListProvider(UserNotifications)
const ctaLink = screen.getByRole('link', {
name: 'Get Writefull Premium',
})
expect(ctaLink.getAttribute('href')).to.equal(
'https://my.writefull.com/plans'
)
})
it('dismisses the banner when the close button is clicked', function () {
localStorage.setItem(
'writefull_promo_scheduled_at',
new Date(Date.now() - 1000)
)
renderWithinProjectListProvider(UserNotifications)
screen.getByRole('link', { name: /Writefull/ })
const closeButton = screen.getByRole('button', { name: 'Close' })
fireEvent.click(closeButton)
expect(screen.queryByRole('link', { name: /Writefull/ })).to.be.null
expect(localStorage.getItem('has_dismissed_writefull_promo_banner'))
.to.exist
expect(localStorage.getItem('writefull_promo_scheduled_at')).not.to
.exist
})
it("doesn't show the banner if it has been dismissed", function () {
localStorage.setItem(
'writefull_promo_scheduled_at',
new Date(Date.now() - 1000)
)
localStorage.setItem(
'has_dismissed_writefull_promo_banner',
new Date(Date.now() - 500)
)
renderWithinProjectListProvider(UserNotifications)
expect(screen.queryByRole('link', { name: /Writefull/ })).to.be.null
})
})
describe('when the writefull integration is not enabled', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-user', {
writefull: { enabled: false },
})
})
it("doesn't show the banner", function () {
renderWithinProjectListProvider(UserNotifications)
expect(screen.queryByRole('link', { name: /Writefull/ })).to.be.null
})
})
})
})
describe('GroupSsoSetupSuccess', function () {
beforeEach(function () {
window.metaAttributesCache = window.metaAttributesCache || new Map()