Merge pull request #25505 from overleaf/jdt-redirect-to-wf-based-on-prem-src

Manage on Writefull should provide the right instructions based on premiumSource

GitOrigin-RevId: bc6dcc5962d18220c445315acbb3b4040ff23d5d
This commit is contained in:
Jimmy Domagala-Tang
2025-05-21 09:39:15 -04:00
committed by Copybot
parent e0f3bea9ad
commit d6cd041704
9 changed files with 59 additions and 29 deletions

View File

@@ -197,14 +197,6 @@ async function doSyncFromV1(v1UserId) {
return refreshFeatures(user._id, 'sync-v1')
}
async function hasFeaturesViaWritefull(userId) {
const user = await UserGetter.promises.getUser(userId, {
_id: 1,
writefull: 1,
})
return Boolean(user?.writefull?.isPremium)
}
module.exports = {
featuresEpochIsCurrent,
computeFeatures: callbackify(computeFeatures),
@@ -217,12 +209,10 @@ module.exports = {
'featuresChanged',
]),
scheduleRefreshFeatures: callbackify(scheduleRefreshFeatures),
hasFeaturesViaWritefull: callbackify(hasFeaturesViaWritefull),
promises: {
computeFeatures,
refreshFeatures,
scheduleRefreshFeatures,
doSyncFromV1,
hasFeaturesViaWritefull,
},
}

View File

@@ -26,6 +26,7 @@ const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
const PlansLocator = require('./PlansLocator')
const PaymentProviderEntities = require('./PaymentProviderEntities')
const { User } = require('../../models/User')
const UserGetter = require('../User/UserGetter')
/**
* @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview'
@@ -154,9 +155,10 @@ async function userSubscriptionPage(req, res) {
'Failed to list groups with group settings enabled for advertising'
)
}
const hasAiAssistViaWritefull =
await FeaturesUpdater.promises.hasFeaturesViaWritefull(user._id)
const {
isPremium: hasAiAssistViaWritefull,
premiumSource: aiAssistViaWritefullSource,
} = await UserGetter.promises.getWritefullData(user._id)
const data = {
title: 'your_subscription',
@@ -181,6 +183,7 @@ async function userSubscriptionPage(req, res) {
isManagedAccount: !!req.managedBy,
userRestrictions: Array.from(req.userRestrictions || []),
hasAiAssistViaWritefull,
aiAssistViaWritefullSource,
}
res.render('subscriptions/dashboard-react', data)
}
@@ -346,8 +349,8 @@ async function previewAddonPurchase(req, res) {
subscriptionChange =
await SubscriptionHandler.promises.previewAddonPurchase(userId, addOnCode)
const hasAiAssistViaWritefull =
await FeaturesUpdater.promises.hasFeaturesViaWritefull(userId)
const { isPremium: hasAiAssistViaWritefull } =
await UserGetter.promises.getWritefullData(userId)
const isAiUpgrade =
PaymentProviderEntities.subscriptionChangeIsAiAssistUpgrade(
subscriptionChange

View File

@@ -137,6 +137,19 @@ async function getSsoUsersAtInstitution(institutionId, projection) {
).exec()
}
async function getWritefullData(userId) {
const user = await UserGetter.promises.getUser(userId, {
writefull: 1,
})
if (!user) {
throw new Error('user not found')
}
return {
isPremium: Boolean(user?.writefull?.isPremium),
premiumSource: user.writefull.premiumSource || null,
}
}
const UserGetter = {
getSsoUsersAtInstitution: callbackify(getSsoUsersAtInstitution),
@@ -271,6 +284,7 @@ const UserGetter = {
callback(error)
})
},
getWritefullData: callbackify(getWritefullData),
}
const decorateFullEmails = (
@@ -346,10 +360,16 @@ const decorateFullEmails = (
}
UserGetter.promises = promisifyAll(UserGetter, {
without: ['getSsoUsersAtInstitution', 'getUserFullEmails', 'getUserFeatures'],
without: [
'getSsoUsersAtInstitution',
'getUserFullEmails',
'getUserFeatures',
'getWritefullData',
],
})
UserGetter.promises.getUserFullEmails = getUserFullEmails
UserGetter.promises.getSsoUsersAtInstitution = getSsoUsersAtInstitution
UserGetter.promises.getUserFeatures = getUserFeatures
UserGetter.promises.getWritefullData = getWritefullData
module.exports = UserGetter

View File

@@ -23,6 +23,7 @@ block append meta
meta(name="ol-showGroupDiscount" data-type="boolean", content=showGroupDiscount)
meta(name="ol-groupSettingsEnabledFor" data-type="json" content=groupSettingsEnabledFor)
meta(name="ol-hasAiAssistViaWritefull" data-type="boolean", content=hasAiAssistViaWritefull)
meta(name="ol-aiAssistViaWritefullSource" data-type="string", content=aiAssistViaWritefullSource)
meta(name="ol-user" data-type="json" content=user)
if (personalSubscription && personalSubscription.payment)
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)

View File

@@ -104,7 +104,8 @@
"aggregate_to": "",
"agree": "",
"agree_with_the_terms": "",
"ai_assist_in_overleaf_is_included_via_writefull": "",
"ai_assist_in_overleaf_is_included_via_writefull_groups": "",
"ai_assist_in_overleaf_is_included_via_writefull_individual": "",
"ai_assistance_to_help_you": "",
"ai_based_language_tools": "",
"ai_can_make_mistakes": "",

View File

@@ -13,14 +13,18 @@ import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap'
import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
import MaterialIcon from '@/shared/components/material-icon'
import { ADD_ON_NAME } from '@/features/subscription/data/add-on-codes'
import getMeta from '@/utils/meta'
function WritefullBundleManagementModal() {
const modalId: SubscriptionDashModalIds = 'manage-on-writefull'
const { t } = useTranslation()
const { handleCloseModal, modalIdShown } = useSubscriptionDashboardContext()
const aiAssistViaWritefullSource = getMeta('ol-aiAssistViaWritefullSource')
if (modalIdShown !== modalId) return null
const individualWFSubscription = aiAssistViaWritefullSource === 'individual'
return (
<OLModal
id={modalId}
@@ -34,22 +38,28 @@ function WritefullBundleManagementModal() {
</OLModalHeader>
<OLModalBody>
<p>{t('ai_assist_in_overleaf_is_included_via_writefull')}</p>
<p>
{individualWFSubscription
? t('ai_assist_in_overleaf_is_included_via_writefull_individual')
: t('ai_assist_in_overleaf_is_included_via_writefull_groups')}
</p>
</OLModalBody>
<OLModalFooter>
<OLButton variant="secondary" onClick={handleCloseModal}>
{t('back')}
</OLButton>
<OLButton
variant="primary"
onClick={handleCloseModal}
href="https://my.writefull.com/account"
target="_blank"
rel="noreferrer"
>
{t('go_to_writefull')}
</OLButton>
{individualWFSubscription && (
<OLButton
variant="primary"
onClick={handleCloseModal}
href="https://my.writefull.com/account"
target="_blank"
rel="noreferrer"
>
{t('go_to_writefull')}
</OLButton>
)}
</OLModalFooter>
</OLModal>
)

View File

@@ -59,6 +59,7 @@ export interface Meta {
string,
{ annual: string; monthly: string; annualDividedByTwelve: string }
>
'ol-aiAssistViaWritefullSource': string
'ol-allInReconfirmNotificationPeriods': UserEmailData[]
'ol-allowedExperiments': string[]
'ol-allowedImageNames': AllowedImageName[]
@@ -81,6 +82,7 @@ export interface Meta {
'ol-cannot-reactivate-subscription': boolean
'ol-cannot-use-ai': boolean
'ol-chatEnabled': boolean
'ol-compilesUserContentDomain': string
'ol-countryCode': PricingFormState['country']
'ol-couponCode': PricingFormState['coupon']

View File

@@ -125,7 +125,8 @@
"aggregate_to": "to",
"agree": "Agree",
"agree_with_the_terms": "I agree with the Overleaf terms",
"ai_assist_in_overleaf_is_included_via_writefull": "AI Assist in Overleaf is included as part of your Writefull subscription. You can cancel or manage your access to AI Assist in your Writefull subscription settings.",
"ai_assist_in_overleaf_is_included_via_writefull_groups": "AI Assist in Overleaf is included as part of your group or organizations Writefull subscription. To make changes youll need to speak to your subscription admin",
"ai_assist_in_overleaf_is_included_via_writefull_individual": "AI Assist in Overleaf is included as part of your Writefull subscription. You can cancel or manage your access to AI Assist in your Writefull subscription settings.",
"ai_assistance_to_help_you": "AI assistance to help you fix LaTeX errors",
"ai_based_language_tools": "AI-based language tools tailored to research writing",
"ai_can_make_mistakes": "AI can make mistakes. Review fixes before you apply them.",

View File

@@ -127,6 +127,9 @@ describe('SubscriptionController', function () {
getUser: sinon.stub().callsArgWith(2, null, this.user),
promises: {
getUser: sinon.stub().resolves(this.user),
getWritefullData: sinon
.stub()
.resolves({ isPremium: false, premiumSource: null }),
},
}
this.SplitTestV2Hander = {
@@ -157,7 +160,6 @@ describe('SubscriptionController', function () {
},
'./FeaturesUpdater': (this.FeaturesUpdater = {
promises: {
hasFeaturesViaWritefull: sinon.stub().resolves(false),
refreshFeatures: sinon.stub().resolves({ features: {} }),
},
}),