diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index eff88d45fb..a8c27f705f 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -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, }, } diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 6116a71174..db278b23c0 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -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 diff --git a/services/web/app/src/Features/User/UserGetter.js b/services/web/app/src/Features/User/UserGetter.js index 9be0c64b9a..fa46492e04 100644 --- a/services/web/app/src/Features/User/UserGetter.js +++ b/services/web/app/src/Features/User/UserGetter.js @@ -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 diff --git a/services/web/app/views/subscriptions/dashboard-react.pug b/services/web/app/views/subscriptions/dashboard-react.pug index dab505e4e5..d6a1bff49c 100644 --- a/services/web/app/views/subscriptions/dashboard-react.pug +++ b/services/web/app/views/subscriptions/dashboard-react.pug @@ -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) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index a8fd6b8169..c64817b94c 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -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": "", diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx index 7d7c971197..d5a615821b 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx @@ -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 ( -

{t('ai_assist_in_overleaf_is_included_via_writefull')}

+

+ {individualWFSubscription + ? t('ai_assist_in_overleaf_is_included_via_writefull_individual') + : t('ai_assist_in_overleaf_is_included_via_writefull_groups')} +

{t('back')} - - {t('go_to_writefull')} - + {individualWFSubscription && ( + + {t('go_to_writefull')} + + )}
) diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 2e0e40c49d..9461635625 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -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'] diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 5e747b59f8..4729f54756 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -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 organization’s Writefull subscription. To make changes you’ll 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.", diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 5b6e86663e..b3ae6610e1 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -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: {} }), }, }),