mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #24697 from overleaf/jdt-show-addons-via-wf
Display When Ai Assist Is Granted Via Writefull GitOrigin-RevId: 91f6e1843e2e1d1f7b3a49d95f31603e838c5545
This commit is contained in:
committed by
Copybot
parent
d492512d9e
commit
f95bf41824
@@ -154,6 +154,9 @@ async function userSubscriptionPage(req, res) {
|
||||
)
|
||||
}
|
||||
|
||||
const hasAiAssistViaWritefull =
|
||||
await FeaturesUpdater.promises.hasFeaturesViaWritefull(user._id)
|
||||
|
||||
const data = {
|
||||
title: 'your_subscription',
|
||||
plans: plansData?.plans,
|
||||
@@ -176,6 +179,7 @@ async function userSubscriptionPage(req, res) {
|
||||
groupSettingsEnabledFor,
|
||||
isManagedAccount: !!req.managedBy,
|
||||
userRestrictions: Array.from(req.userRestrictions || []),
|
||||
hasAiAssistViaWritefull,
|
||||
}
|
||||
res.render('subscriptions/dashboard-react', data)
|
||||
}
|
||||
@@ -322,13 +326,13 @@ async function previewAddonPurchase(req, res) {
|
||||
subscriptionChange =
|
||||
await SubscriptionHandler.promises.previewAddonPurchase(userId, addOnCode)
|
||||
|
||||
const hasBundleViaWritefull =
|
||||
const hasAiAssistViaWritefull =
|
||||
await FeaturesUpdater.promises.hasFeaturesViaWritefull(userId)
|
||||
const isAiUpgrade =
|
||||
PaymentProviderEntities.subscriptionChangeIsAiAssistUpgrade(
|
||||
subscriptionChange
|
||||
)
|
||||
if (hasBundleViaWritefull && isAiUpgrade) {
|
||||
if (hasAiAssistViaWritefull && isAiUpgrade) {
|
||||
return res.redirect(
|
||||
'/user/subscription?redirect-reason=writefull-entitled'
|
||||
)
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
extends ../layout-react
|
||||
|
||||
block entrypointVar
|
||||
- entrypoint = 'pages/user/subscription/dashboard'
|
||||
- entrypoint = 'pages/user/subscription/dashboard'
|
||||
|
||||
block head-scripts
|
||||
script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js")
|
||||
script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js")
|
||||
|
||||
block append meta
|
||||
meta(name="ol-subscription" data-type="json" content=personalSubscription)
|
||||
meta(name="ol-userCanExtendTrial" data-type="boolean" content=userCanExtendTrial)
|
||||
meta(name="ol-managedGroupSubscriptions" data-type="json" content=managedGroupSubscriptions)
|
||||
meta(name="ol-memberGroupSubscriptions" data-type="json" content=memberGroupSubscriptions)
|
||||
meta(name="ol-managedInstitutions" data-type="json" content=managedInstitutions)
|
||||
meta(name="ol-managedPublishers" data-type="json" content=managedPublishers)
|
||||
meta(name="ol-planCodesChangingAtTermEnd" data-type="json", content=planCodesChangingAtTermEnd)
|
||||
meta(name="ol-currentInstitutionsWithLicence" data-type="json" content=currentInstitutionsWithLicence)
|
||||
meta(name="ol-hasSubscription" data-type="boolean" content=hasSubscription)
|
||||
meta(name="ol-fromPlansPage" data-type="boolean" content=fromPlansPage)
|
||||
meta(name="ol-plans", data-type="json" content=plans)
|
||||
meta(name="ol-groupSettingsAdvertisedFor", data-type="json" content=groupSettingsAdvertisedFor)
|
||||
meta(name="ol-canUseFlexibleLicensing", data-type="boolean", content=canUseFlexibleLicensing)
|
||||
meta(name="ol-showGroupDiscount", data-type="boolean", content=showGroupDiscount)
|
||||
meta(name="ol-groupSettingsEnabledFor", data-type="json" content=groupSettingsEnabledFor)
|
||||
meta(name="ol-user" data-type="json" content=user)
|
||||
if (personalSubscription && personalSubscription.payment)
|
||||
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
|
||||
meta(name="ol-recommendedCurrency" content=personalSubscription.payment.currency)
|
||||
meta(name="ol-groupPlans" data-type="json" content=groupPlans)
|
||||
meta(name="ol-subscription" data-type="json" content=personalSubscription)
|
||||
meta(name="ol-userCanExtendTrial" data-type="boolean" content=userCanExtendTrial)
|
||||
meta(name="ol-managedGroupSubscriptions" data-type="json" content=managedGroupSubscriptions)
|
||||
meta(name="ol-memberGroupSubscriptions" data-type="json" content=memberGroupSubscriptions)
|
||||
meta(name="ol-managedInstitutions" data-type="json" content=managedInstitutions)
|
||||
meta(name="ol-managedPublishers" data-type="json" content=managedPublishers)
|
||||
meta(name="ol-planCodesChangingAtTermEnd" data-type="json", content=planCodesChangingAtTermEnd)
|
||||
meta(name="ol-currentInstitutionsWithLicence" data-type="json" content=currentInstitutionsWithLicence)
|
||||
meta(name="ol-hasSubscription" data-type="boolean" content=hasSubscription)
|
||||
meta(name="ol-fromPlansPage" data-type="boolean" content=fromPlansPage)
|
||||
meta(name="ol-plans" data-type="json" content=plans)
|
||||
meta(name="ol-groupSettingsAdvertisedFor" data-type="json" content=groupSettingsAdvertisedFor)
|
||||
meta(name="ol-canUseFlexibleLicensing" data-type="boolean", content=canUseFlexibleLicensing)
|
||||
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-user" data-type="json" content=user)
|
||||
if (personalSubscription && personalSubscription.payment)
|
||||
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
|
||||
meta(name="ol-recommendedCurrency" content=personalSubscription.payment.currency)
|
||||
meta(name="ol-groupPlans" data-type="json" content=groupPlans)
|
||||
|
||||
block content
|
||||
main.content.content-alt#main-content
|
||||
#subscription-dashboard-root
|
||||
main.content.content-alt#main-content
|
||||
#subscription-dashboard-root
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
"aggregate_changed": "",
|
||||
"aggregate_to": "",
|
||||
"agree_with_the_terms": "",
|
||||
"ai_assist_in_overleaf_is_included_via_writefull": "",
|
||||
"ai_assistance_to_help_you": "",
|
||||
"ai_based_language_tools": "",
|
||||
"ai_can_make_mistakes": "",
|
||||
@@ -671,6 +672,7 @@
|
||||
"go_to_pdf_location_in_code": "",
|
||||
"go_to_settings": "",
|
||||
"go_to_subscriptions": "",
|
||||
"go_to_writefull": "",
|
||||
"good_news_you_already_purchased_this_add_on": "",
|
||||
"good_news_you_are_already_receiving_this_add_on_via_writefull": "",
|
||||
"group_admin": "",
|
||||
@@ -789,6 +791,7 @@
|
||||
"include_results_from_your_reference_manager": "",
|
||||
"include_results_from_your_x_account": "",
|
||||
"include_the_error_message_and_ai_response": "",
|
||||
"included_as_part_of_your_writefull_subscription": "",
|
||||
"increase_indent": "",
|
||||
"increased_compile_timeout": "",
|
||||
"inline": "",
|
||||
@@ -979,6 +982,7 @@
|
||||
"manage_publisher_managers": "",
|
||||
"manage_sessions": "",
|
||||
"manage_subscription": "",
|
||||
"manage_your_ai_assist_add_on": "",
|
||||
"managed": "",
|
||||
"managed_user_accounts": "",
|
||||
"managed_user_invite_has_been_sent_to_email": "",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ConfirmChangePlanModal } from './change-plan/modals/confirm-change-plan
|
||||
import { KeepCurrentPlanModal } from './change-plan/modals/keep-current-plan-modal'
|
||||
import { ChangeToGroupModal } from './change-plan/modals/change-to-group-modal'
|
||||
import { CancelAiAddOnModal } from '@/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal'
|
||||
import { WritefullBundleManagementModal } from '@/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import isInFreeTrial from '../../../../util/is-in-free-trial'
|
||||
import AddOns from '@/features/subscription/components/dashboard/states/active/add-ons'
|
||||
@@ -71,6 +72,7 @@ export function ActiveSubscriptionNew({
|
||||
}
|
||||
|
||||
const handlePlanChange = () => setModalIdShown('change-plan')
|
||||
const handleManageOnWritefull = () => setModalIdShown('manage-on-writefull')
|
||||
const handleCancelClick = (addOnCode: string) => {
|
||||
if ([AI_STANDALONE_PLAN_CODE, AI_ADD_ON_CODE].includes(addOnCode)) {
|
||||
setModalIdShown('cancel-ai-add-on')
|
||||
@@ -248,6 +250,7 @@ export function ActiveSubscriptionNew({
|
||||
subscription={subscription}
|
||||
onStandalonePlan={onStandalonePlan}
|
||||
handleCancelClick={handleCancelClick}
|
||||
handleManageOnWritefull={handleManageOnWritefull}
|
||||
/>
|
||||
|
||||
<ChangePlanModal />
|
||||
@@ -255,6 +258,7 @@ export function ActiveSubscriptionNew({
|
||||
<KeepCurrentPlanModal />
|
||||
<ChangeToGroupModal />
|
||||
<CancelAiAddOnModal />
|
||||
<WritefullBundleManagementModal />
|
||||
<PauseSubscriptionModal />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap-5'
|
||||
import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
@@ -16,6 +17,7 @@ type AddOnsProps = {
|
||||
subscription: PaidSubscription
|
||||
onStandalonePlan: boolean
|
||||
handleCancelClick: (code: string) => void
|
||||
handleManageOnWritefull: () => void
|
||||
}
|
||||
|
||||
type AddOnProps = {
|
||||
@@ -98,12 +100,60 @@ function AddOn({
|
||||
)
|
||||
}
|
||||
|
||||
function WritefullGrantedAddOn({
|
||||
handleManageOnWritefull,
|
||||
}: {
|
||||
handleManageOnWritefull: () => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="add-on-card">
|
||||
<div>
|
||||
<img
|
||||
alt="sparkle"
|
||||
className="add-on-card-icon"
|
||||
src={sparkle}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="add-on-card-content">
|
||||
<div className="heading">{ADD_ON_NAME}</div>
|
||||
<div className="description small mt-1">
|
||||
{t('included_as_part_of_your_writefull_subscription')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ms-auto">
|
||||
<Dropdown align="end">
|
||||
<DropdownToggle
|
||||
id="add-on-dropdown-toggle"
|
||||
className="add-on-options-toggle"
|
||||
variant="secondary"
|
||||
>
|
||||
<MaterialIcon
|
||||
type="more_vert"
|
||||
accessibilityLabel={t('more_options')}
|
||||
/>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>
|
||||
<OLDropdownMenuItem tabIndex={-1} onClick={handleManageOnWritefull}>
|
||||
{t('manage_subscription')}
|
||||
</OLDropdownMenuItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AddOns({
|
||||
subscription,
|
||||
onStandalonePlan,
|
||||
handleCancelClick,
|
||||
handleManageOnWritefull,
|
||||
}: AddOnsProps) {
|
||||
const { t } = useTranslation()
|
||||
const hasAiAssistViaWritefull = getMeta('ol-hasAiAssistViaWritefull')
|
||||
const addOnsDisplayPrices = onStandalonePlan
|
||||
? {
|
||||
[AI_STANDALONE_PLAN_CODE]: subscription.payment.displayPrice,
|
||||
@@ -113,26 +163,35 @@ function AddOns({
|
||||
? [{ addOnCode: AI_STANDALONE_PLAN_CODE }]
|
||||
: subscription.addOns?.filter(addOn => addOn.addOnCode !== LICENSE_ADD_ON)
|
||||
|
||||
const hasAddons =
|
||||
(addOnsToDisplay && addOnsToDisplay.length > 0) || hasAiAssistViaWritefull
|
||||
return (
|
||||
<>
|
||||
<h2 className="h3 fw-bold">{t('add_ons')}</h2>
|
||||
{addOnsToDisplay && addOnsToDisplay.length > 0 ? (
|
||||
addOnsToDisplay.map(addOn => (
|
||||
<AddOn
|
||||
addOnCode={addOn.addOnCode}
|
||||
key={addOn.addOnCode}
|
||||
isAnnual={Boolean(subscription.plan.annual)}
|
||||
handleCancelClick={handleCancelClick}
|
||||
pendingCancellation={
|
||||
subscription.pendingPlan !== undefined &&
|
||||
(subscription.pendingPlan.addOns ?? []).every(
|
||||
pendingAddOn => pendingAddOn.code !== addOn.addOnCode
|
||||
)
|
||||
}
|
||||
displayPrice={addOnsDisplayPrices[addOn.addOnCode]}
|
||||
nextBillingDate={subscription.payment.nextPaymentDueDate}
|
||||
/>
|
||||
))
|
||||
{hasAddons ? (
|
||||
<>
|
||||
{addOnsToDisplay?.map(addOn => (
|
||||
<AddOn
|
||||
addOnCode={addOn.addOnCode}
|
||||
key={addOn.addOnCode}
|
||||
isAnnual={Boolean(subscription.plan.annual)}
|
||||
handleCancelClick={handleCancelClick}
|
||||
pendingCancellation={
|
||||
subscription.pendingPlan !== undefined &&
|
||||
(subscription.pendingPlan.addOns ?? []).every(
|
||||
pendingAddOn => pendingAddOn.code !== addOn.addOnCode
|
||||
)
|
||||
}
|
||||
displayPrice={addOnsDisplayPrices[addOn.addOnCode]}
|
||||
nextBillingDate={subscription.payment.nextPaymentDueDate}
|
||||
/>
|
||||
))}
|
||||
{hasAiAssistViaWritefull && (
|
||||
<WritefullGrantedAddOn
|
||||
handleManageOnWritefull={handleManageOnWritefull}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p>{t('you_dont_have_any_add_ons_on_your_account')}</p>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SubscriptionDashModalIds } from '../../../../../../../../../../types/subscription/dashboard/modal-ids'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export function WritefullBundleManagementModal() {
|
||||
const modalId: SubscriptionDashModalIds = 'manage-on-writefull'
|
||||
const { t } = useTranslation()
|
||||
const { handleCloseModal, modalIdShown } = useSubscriptionDashboardContext()
|
||||
|
||||
if (modalIdShown !== modalId) return null
|
||||
|
||||
return (
|
||||
<OLModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('manage_your_ai_assist_add_on')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
|
||||
<OLModalBody>
|
||||
<p>{t('ai_assist_in_overleaf_is_included_via_writefull')}</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>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
@@ -115,6 +115,7 @@ export interface Meta {
|
||||
'ol-groupSsoSetupSuccess': boolean
|
||||
'ol-groupSubscriptionsPendingEnrollment': PendingGroupSubscriptionEnrollment[]
|
||||
'ol-groupsAndEnterpriseBannerVariant': GroupsAndEnterpriseBannerVariant
|
||||
'ol-hasAiAssistViaWritefull': boolean
|
||||
'ol-hasGroupSSOFeature': boolean
|
||||
'ol-hasIndividualRecurlySubscription': boolean
|
||||
'ol-hasManagedUsersFeature': boolean
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
"aggregate_changed": "Changed",
|
||||
"aggregate_to": "to",
|
||||
"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_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.",
|
||||
@@ -886,6 +887,7 @@
|
||||
"go_to_previous_page": "Go to previous page",
|
||||
"go_to_settings": "Go to settings",
|
||||
"go_to_subscriptions": "Go to Subscriptions",
|
||||
"go_to_writefull": "Go to Writefull",
|
||||
"good_news_you_already_purchased_this_add_on": "Good news! You already have this add-on, so no need to pay again.",
|
||||
"good_news_you_are_already_receiving_this_add_on_via_writefull": "Good news! You already have this add-on via your Writefull subscription. No need to pay again.",
|
||||
"great_for_getting_started": "Great for getting started",
|
||||
@@ -1018,6 +1020,7 @@
|
||||
"include_results_from_your_reference_manager": "Include results from your reference manager",
|
||||
"include_results_from_your_x_account": "Include results from your __provider__ account",
|
||||
"include_the_error_message_and_ai_response": "Include the error message and AI response",
|
||||
"included_as_part_of_your_writefull_subscription": "Included as part of your Writefull subscription",
|
||||
"increase_indent": "Increase indentation",
|
||||
"increased_compile_timeout": "Increased compile timeout",
|
||||
"individuals": "Individuals",
|
||||
@@ -1288,6 +1291,7 @@
|
||||
"manage_publisher_managers": "Manage publisher managers",
|
||||
"manage_sessions": "Manage Your Sessions",
|
||||
"manage_subscription": "Manage subscription",
|
||||
"manage_your_ai_assist_add_on": "Manage your AI Assist add-on",
|
||||
"managed": "Managed",
|
||||
"managed_user_accounts": "Managed user accounts",
|
||||
"managed_user_invite_has_been_sent_to_email": "Managed User invite has been sent to <0>__email__</0>",
|
||||
|
||||
@@ -155,7 +155,11 @@ describe('SubscriptionController', function () {
|
||||
'./RecurlyEventHandler': {
|
||||
sendRecurlyAnalyticsEvent: sinon.stub().resolves(),
|
||||
},
|
||||
'./FeaturesUpdater': (this.FeaturesUpdater = {}),
|
||||
'./FeaturesUpdater': (this.FeaturesUpdater = {
|
||||
promises: {
|
||||
hasFeaturesViaWritefull: sinon.stub().resolves(false),
|
||||
},
|
||||
}),
|
||||
'./GroupPlansData': (this.GroupPlansData = {}),
|
||||
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
|
||||
'../Errors/HttpErrorHandler': (this.HttpErrorHandler = {
|
||||
|
||||
@@ -5,5 +5,6 @@ export type SubscriptionDashModalIds =
|
||||
| 'leave-group'
|
||||
| 'change-plan'
|
||||
| 'cancel-ai-add-on'
|
||||
| 'manage-on-writefull'
|
||||
| 'pause-subscription'
|
||||
| 'unpause-subscription'
|
||||
|
||||
Reference in New Issue
Block a user