mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
Merge pull request #24164 from overleaf/ds-group-discount-10-percent-february
Group plans - Reintroduce 10% discount for group plans (Part 1, 2 & 3 Combined) GitOrigin-RevId: f3a59a65bbd300cc06f70e179e794c32ed7970ce
This commit is contained in:
@@ -48,6 +48,14 @@ async function userSubscriptionPage(req, res) {
|
|||||||
|
|
||||||
await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription')
|
await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription')
|
||||||
|
|
||||||
|
const groupPricingDiscount = await SplitTestHandler.promises.getAssignment(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
'group-discount-10'
|
||||||
|
)
|
||||||
|
|
||||||
|
const showGroupDiscount = groupPricingDiscount.variant === 'enabled'
|
||||||
|
|
||||||
const { variant: flexibleLicensingVariant } =
|
const { variant: flexibleLicensingVariant } =
|
||||||
await SplitTestHandler.promises.getAssignment(
|
await SplitTestHandler.promises.getAssignment(
|
||||||
req,
|
req,
|
||||||
@@ -167,6 +175,7 @@ async function userSubscriptionPage(req, res) {
|
|||||||
managedGroupSubscriptions,
|
managedGroupSubscriptions,
|
||||||
managedInstitutions,
|
managedInstitutions,
|
||||||
managedPublishers,
|
managedPublishers,
|
||||||
|
showGroupDiscount,
|
||||||
currentInstitutionsWithLicence,
|
currentInstitutionsWithLicence,
|
||||||
canUseFlexibleLicensing:
|
canUseFlexibleLicensing:
|
||||||
personalSubscription?.plan?.canUseFlexibleLicensing,
|
personalSubscription?.plan?.canUseFlexibleLicensing,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ block append meta
|
|||||||
meta(name="ol-plans", data-type="json" content=plans)
|
meta(name="ol-plans", data-type="json" content=plans)
|
||||||
meta(name="ol-groupSettingsAdvertisedFor", data-type="json" content=groupSettingsAdvertisedFor)
|
meta(name="ol-groupSettingsAdvertisedFor", data-type="json" content=groupSettingsAdvertisedFor)
|
||||||
meta(name="ol-canUseFlexibleLicensing", data-type="boolean", content=canUseFlexibleLicensing)
|
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-groupSettingsEnabledFor", data-type="json" content=groupSettingsEnabledFor)
|
||||||
meta(name="ol-user" data-type="json" content=user)
|
meta(name="ol-user" data-type="json" content=user)
|
||||||
if (personalSubscription && personalSubscription.recurly)
|
if (personalSubscription && personalSubscription.recurly)
|
||||||
|
|||||||
@@ -137,6 +137,7 @@
|
|||||||
"anyone_with_link_can_view": "",
|
"anyone_with_link_can_view": "",
|
||||||
"app_on_x": "",
|
"app_on_x": "",
|
||||||
"appearance": "",
|
"appearance": "",
|
||||||
|
"apply_educational_discount_description_with_group_discount": "",
|
||||||
"apply_suggestion": "",
|
"apply_suggestion": "",
|
||||||
"archive": "",
|
"archive": "",
|
||||||
"archive_projects": "",
|
"archive_projects": "",
|
||||||
@@ -199,8 +200,6 @@
|
|||||||
"cancel": "",
|
"cancel": "",
|
||||||
"cancel_add_on": "",
|
"cancel_add_on": "",
|
||||||
"cancel_anytime": "",
|
"cancel_anytime": "",
|
||||||
"cancel_group_price_warning": "",
|
|
||||||
"cancel_group_price_warning_heading": "",
|
|
||||||
"cancel_my_account": "",
|
"cancel_my_account": "",
|
||||||
"cancel_my_subscription": "",
|
"cancel_my_subscription": "",
|
||||||
"cancel_personal_subscription_first": "",
|
"cancel_personal_subscription_first": "",
|
||||||
@@ -892,7 +891,6 @@
|
|||||||
"let_us_know_what_you_think": "",
|
"let_us_know_what_you_think": "",
|
||||||
"lets_get_those_premium_features": "",
|
"lets_get_those_premium_features": "",
|
||||||
"library": "",
|
"library": "",
|
||||||
"license_for_educational_purposes_confirmation": "",
|
|
||||||
"limited_document_history": "",
|
"limited_document_history": "",
|
||||||
"limited_offer": "",
|
"limited_offer": "",
|
||||||
"limited_to_n_editors": "",
|
"limited_to_n_editors": "",
|
||||||
@@ -1417,6 +1415,7 @@
|
|||||||
"save_or_cancel-cancel": "",
|
"save_or_cancel-cancel": "",
|
||||||
"save_or_cancel-or": "",
|
"save_or_cancel-or": "",
|
||||||
"save_or_cancel-save": "",
|
"save_or_cancel-save": "",
|
||||||
|
"save_x_or_more": "",
|
||||||
"saving": "",
|
"saving": "",
|
||||||
"saving_notification_with_seconds": "",
|
"saving_notification_with_seconds": "",
|
||||||
"search": "",
|
"search": "",
|
||||||
|
|||||||
-21
@@ -15,8 +15,6 @@ import ExtendTrialButton from './extend-trial-button'
|
|||||||
import { useLocation } from '../../../../../../../shared/hooks/use-location'
|
import { useLocation } from '../../../../../../../shared/hooks/use-location'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
import moment from 'moment'
|
|
||||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
|
||||||
|
|
||||||
const planCodeToDowngradeTo = 'paid-personal'
|
const planCodeToDowngradeTo = 'paid-personal'
|
||||||
|
|
||||||
@@ -172,12 +170,6 @@ export function CancelSubscription() {
|
|||||||
return <LoadingSpinner />
|
return <LoadingSpinner />
|
||||||
}
|
}
|
||||||
|
|
||||||
const startDate = moment.utc(personalSubscription.recurly.account.created_at)
|
|
||||||
const pricingChangeEffectiveDate = moment.utc('2025-01-08T12:00:00Z')
|
|
||||||
const displayPricingWarning =
|
|
||||||
personalSubscription.plan.groupPlan &&
|
|
||||||
startDate.isBefore(pricingChangeEffectiveDate)
|
|
||||||
|
|
||||||
async function handleCancelSubscription() {
|
async function handleCancelSubscription() {
|
||||||
try {
|
try {
|
||||||
await runAsyncCancel(postJSON(cancelSubscriptionUrl))
|
await runAsyncCancel(postJSON(cancelSubscriptionUrl))
|
||||||
@@ -191,19 +183,6 @@ export function CancelSubscription() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{displayPricingWarning && (
|
|
||||||
<OLNotification
|
|
||||||
type="warning"
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<h2 className="pricing-warning-heading">
|
|
||||||
{t('cancel_group_price_warning_heading')}
|
|
||||||
</h2>
|
|
||||||
<p>{t('cancel_group_price_warning')}</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p>
|
<p>
|
||||||
<strong>{t('wed_love_you_to_stay')}</strong>
|
<strong>{t('wed_love_you_to_stay')}</strong>
|
||||||
|
|||||||
+9
-14
@@ -118,6 +118,7 @@ export function ChangeToGroupModal() {
|
|||||||
const { modal: contactModal, showModal: showContactModal } =
|
const { modal: contactModal, showModal: showContactModal } =
|
||||||
useContactUsModal({ autofillProjectUrl: false })
|
useContactUsModal({ autofillProjectUrl: false })
|
||||||
const groupPlans = getMeta('ol-groupPlans')
|
const groupPlans = getMeta('ol-groupPlans')
|
||||||
|
const showGroupDiscount = getMeta('ol-showGroupDiscount')
|
||||||
const personalSubscription = getMeta('ol-subscription') as RecurlySubscription
|
const personalSubscription = getMeta('ol-subscription') as RecurlySubscription
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
const [inflight, setInflight] = useState(false)
|
const [inflight, setInflight] = useState(false)
|
||||||
@@ -172,6 +173,11 @@ export function ChangeToGroupModal() {
|
|||||||
<OLModalHeader closeButton>
|
<OLModalHeader closeButton>
|
||||||
<OLModalTitle className="lh-sm">
|
<OLModalTitle className="lh-sm">
|
||||||
{t('customize_your_group_subscription')}
|
{t('customize_your_group_subscription')}
|
||||||
|
{showGroupDiscount && (
|
||||||
|
<p className="group-subscription-modal-title-discount">
|
||||||
|
{t('save_x_or_more', { percentage: '10%' })}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</OLModalTitle>
|
</OLModalTitle>
|
||||||
</OLModalHeader>
|
</OLModalHeader>
|
||||||
|
|
||||||
@@ -259,20 +265,9 @@ export function ChangeToGroupModal() {
|
|||||||
setGroupPlanToChangeToUsage('enterprise')
|
setGroupPlanToChangeToUsage('enterprise')
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
label={
|
label={t(
|
||||||
<Trans
|
'apply_educational_discount_description_with_group_discount'
|
||||||
i18nKey="license_for_educational_purposes_confirmation"
|
)}
|
||||||
values={{ percent: educationalPercentDiscount }}
|
|
||||||
shouldUnescape
|
|
||||||
tOptions={{ interpolation: { escapeValue: true } }}
|
|
||||||
components={[
|
|
||||||
/* eslint-disable-next-line react/jsx-key */
|
|
||||||
<strong />,
|
|
||||||
/* eslint-disable-next-line react/jsx-key */
|
|
||||||
<br />,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ export interface Meta {
|
|||||||
'ol-showAiErrorAssistant': boolean
|
'ol-showAiErrorAssistant': boolean
|
||||||
'ol-showBrlGeoBanner': boolean
|
'ol-showBrlGeoBanner': boolean
|
||||||
'ol-showCouponField': boolean
|
'ol-showCouponField': boolean
|
||||||
|
'ol-showGroupDiscount': boolean
|
||||||
'ol-showGroupsAndEnterpriseBanner': boolean
|
'ol-showGroupsAndEnterpriseBanner': boolean
|
||||||
'ol-showInrGeoBanner': boolean
|
'ol-showInrGeoBanner': boolean
|
||||||
'ol-showLATAMBanner': boolean
|
'ol-showLATAMBanner': boolean
|
||||||
|
|||||||
@@ -165,6 +165,22 @@
|
|||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group-discount-bubble {
|
||||||
|
padding: var(--spacing-01) var(--spacing-04);
|
||||||
|
background-color: var(--green-10);
|
||||||
|
color: var(--green-50);
|
||||||
|
border-radius: var(--border-radius-full-new);
|
||||||
|
font-family: 'DM Mono', monospace;
|
||||||
|
font-feature-settings: 'ss05';
|
||||||
|
font-size: var(--font-size-01);
|
||||||
|
line-height: var(--line-height-01);
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
@media (max-width: @screen-xs-max) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: @screen-xs-max) {
|
@media (max-width: @screen-xs-max) {
|
||||||
font-size: var(--font-size-02);
|
font-size: var(--font-size-02);
|
||||||
line-height: var(--line-height-02);
|
line-height: var(--line-height-02);
|
||||||
|
|||||||
@@ -37,6 +37,13 @@
|
|||||||
* MODALS
|
* MODALS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.group-subscription-modal-title-discount {
|
||||||
|
@include body-base;
|
||||||
|
|
||||||
|
margin-top: var(--spacing-02);
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.group-subscription-modal {
|
.group-subscription-modal {
|
||||||
.circle {
|
.circle {
|
||||||
font-size: var(--font-size-06);
|
font-size: var(--font-size-06);
|
||||||
|
|||||||
@@ -169,6 +169,7 @@
|
|||||||
"appearance": "Appearance",
|
"appearance": "Appearance",
|
||||||
"apply_educational_discount": "Apply educational discount",
|
"apply_educational_discount": "Apply educational discount",
|
||||||
"apply_educational_discount_description": "40% discount for groups using __appName__ for teaching",
|
"apply_educational_discount_description": "40% discount for groups using __appName__ for teaching",
|
||||||
|
"apply_educational_discount_description_with_group_discount": "Get a total of 40% off for groups using __appName__ for teaching",
|
||||||
"apply_suggestion": "Apply suggestion",
|
"apply_suggestion": "Apply suggestion",
|
||||||
"april": "April",
|
"april": "April",
|
||||||
"archive": "Archive",
|
"archive": "Archive",
|
||||||
@@ -260,8 +261,6 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"cancel_add_on": "Cancel add-on",
|
"cancel_add_on": "Cancel add-on",
|
||||||
"cancel_anytime": "We’re confident that you’ll love __appName__, but if not you can cancel anytime. We’ll give you your money back, no questions asked, if you let us know within 30 days.",
|
"cancel_anytime": "We’re confident that you’ll love __appName__, but if not you can cancel anytime. We’ll give you your money back, no questions asked, if you let us know within 30 days.",
|
||||||
"cancel_group_price_warning": "Our per-user prices are increasing in 2025. Keep your current rate for existing and new users by staying with us.",
|
|
||||||
"cancel_group_price_warning_heading": "Don’t lose your current pricing",
|
|
||||||
"cancel_my_account": "Cancel my subscription",
|
"cancel_my_account": "Cancel my subscription",
|
||||||
"cancel_my_subscription": "Cancel my subscription",
|
"cancel_my_subscription": "Cancel my subscription",
|
||||||
"cancel_personal_subscription_first": "You already have an individual subscription, would you like us to cancel this first before joining the group licence?",
|
"cancel_personal_subscription_first": "You already have an individual subscription, would you like us to cancel this first before joining the group licence?",
|
||||||
@@ -1173,7 +1172,6 @@
|
|||||||
"libraries": "Libraries",
|
"libraries": "Libraries",
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
"license": "License",
|
"license": "License",
|
||||||
"license_for_educational_purposes_confirmation": "<0>__percent__% educational discount</0><1/>I confirm this subscription is for educational purposes (applies to students or faculty using __appName__ for teaching)",
|
|
||||||
"limited_document_history": "Limited document history",
|
"limited_document_history": "Limited document history",
|
||||||
"limited_to_n_editors": "Limited to __count__ editor",
|
"limited_to_n_editors": "Limited to __count__ editor",
|
||||||
"limited_to_n_editors_per_project": "Limited to __count__ editor per project",
|
"limited_to_n_editors_per_project": "Limited to __count__ editor per project",
|
||||||
@@ -1876,6 +1874,7 @@
|
|||||||
"save_or_cancel-cancel": "Cancel",
|
"save_or_cancel-cancel": "Cancel",
|
||||||
"save_or_cancel-or": "or",
|
"save_or_cancel-or": "or",
|
||||||
"save_or_cancel-save": "Save",
|
"save_or_cancel-save": "Save",
|
||||||
|
"save_x_or_more": "Save __percentage__ or more",
|
||||||
"saving": "Saving",
|
"saving": "Saving",
|
||||||
"saving_notification_with_seconds": "Saving __docname__... (__seconds__ seconds of unsaved changes)",
|
"saving_notification_with_seconds": "Saving __docname__... (__seconds__ seconds of unsaved changes)",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
|
|||||||
+4
-5
@@ -322,7 +322,7 @@ describe('<ChangePlanModal />', function () {
|
|||||||
const standardPlanCollaboratorText = '10 collaborators per project'
|
const standardPlanCollaboratorText = '10 collaborators per project'
|
||||||
const professionalPlanCollaboratorText = 'Unlimited collaborators'
|
const professionalPlanCollaboratorText = 'Unlimited collaborators'
|
||||||
const educationInputLabel =
|
const educationInputLabel =
|
||||||
'40% educational discountI confirm this subscription is for educational purposes (applies to students or faculty using Overleaf for teaching)'
|
'Get a total of 40% off for groups using Overleaf for teaching'
|
||||||
|
|
||||||
let modal: HTMLElement
|
let modal: HTMLElement
|
||||||
async function openModal() {
|
async function openModal() {
|
||||||
@@ -368,15 +368,14 @@ describe('<ChangePlanModal />', function () {
|
|||||||
expect(sizeSelect.value).to.equal('10')
|
expect(sizeSelect.value).to.equal('10')
|
||||||
const sizeOption = within(sizeSelect).getAllByRole('option')
|
const sizeOption = within(sizeSelect).getAllByRole('option')
|
||||||
expect(sizeOption.length).to.equal(groupPlans.sizes.length)
|
expect(sizeOption.length).to.equal(groupPlans.sizes.length)
|
||||||
within(modal).getByText('40% educational discount')
|
within(modal).getByText(
|
||||||
|
'Get a total of 40% off for groups using Overleaf for teaching'
|
||||||
|
)
|
||||||
|
|
||||||
const educationalCheckbox = within(modal).getByRole(
|
const educationalCheckbox = within(modal).getByRole(
|
||||||
'checkbox'
|
'checkbox'
|
||||||
) as HTMLInputElement
|
) as HTMLInputElement
|
||||||
expect(educationalCheckbox.checked).to.be.false
|
expect(educationalCheckbox.checked).to.be.false
|
||||||
within(modal).getByText(
|
|
||||||
'I confirm this subscription is for educational purposes (applies to students or faculty using Overleaf for teaching)'
|
|
||||||
)
|
|
||||||
|
|
||||||
within(modal).getByText(
|
within(modal).getByText(
|
||||||
'Your new subscription will be billed immediately to your current payment method.'
|
'Your new subscription will be billed immediately to your current payment method.'
|
||||||
|
|||||||
Reference in New Issue
Block a user