mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 22:23:42 +01:00
Merge pull request #22184 from overleaf/ls-group-plan-upgrade-page
Group plan upgrade page GitOrigin-RevId: 6c99173c013d84943276dbd43f468026c4d44558
This commit is contained in:
@@ -216,6 +216,30 @@ class RecurlySubscription {
|
|||||||
addOnUpdates,
|
addOnUpdates,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade group plan with the plan code provided
|
||||||
|
*
|
||||||
|
* @param {string} newPlanCode
|
||||||
|
* @return {RecurlySubscriptionChangeRequest}
|
||||||
|
*/
|
||||||
|
getRequestForFlexibleLicensingGroupPlanUpgrade(newPlanCode) {
|
||||||
|
// Ensure all the existing add-ons are added to the new plan
|
||||||
|
const existingAddOns = this.addOns.map(
|
||||||
|
addOn =>
|
||||||
|
new RecurlySubscriptionAddOnUpdate({
|
||||||
|
code: addOn.code,
|
||||||
|
quantity: addOn.quantity,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return new RecurlySubscriptionChangeRequest({
|
||||||
|
subscription: this,
|
||||||
|
timeframe: 'now',
|
||||||
|
addOnUpdates: existingAddOns,
|
||||||
|
planCode: newPlanCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
|
|||||||
import ErrorController from '../Errors/ErrorController.js'
|
import ErrorController from '../Errors/ErrorController.js'
|
||||||
import SalesContactFormController from '../../../../modules/cms/app/src/controllers/SalesContactFormController.mjs'
|
import SalesContactFormController from '../../../../modules/cms/app/src/controllers/SalesContactFormController.mjs'
|
||||||
import UserGetter from '../User/UserGetter.js'
|
import UserGetter from '../User/UserGetter.js'
|
||||||
|
import { Subscription } from '../../models/Subscription.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import { Subscription } from "../../../../types/subscription/dashboard/subscription"
|
* @import { Subscription } from "../../../../types/subscription/dashboard/subscription.js"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,6 +221,36 @@ async function flexibleLicensingSplitTest(req, res, next) {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function subscriptionUpgradePage(req, res) {
|
||||||
|
try {
|
||||||
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
|
const changePreview =
|
||||||
|
await SubscriptionGroupHandler.promises.getGroupPlanUpgradePreview(userId)
|
||||||
|
const olSubscription = await Subscription.findOne({
|
||||||
|
admin_id: userId,
|
||||||
|
}).exec()
|
||||||
|
res.render('subscriptions/upgrade-group-subscription-react', {
|
||||||
|
changePreview,
|
||||||
|
totalLicenses: olSubscription.membersLimit,
|
||||||
|
groupName: olSubscription.teamName,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
logger.err({ error }, 'error loading upgrade subscription page')
|
||||||
|
return res.render('/user/subscription')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upgradeSubscription(req, res) {
|
||||||
|
try {
|
||||||
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
|
await SubscriptionGroupHandler.promises.upgradeGroupPlan(userId)
|
||||||
|
return res.sendStatus(200)
|
||||||
|
} catch (error) {
|
||||||
|
logger.err({ error }, 'error trying to upgrade subscription')
|
||||||
|
return res.sendStatus(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
removeUserFromGroup: expressify(removeUserFromGroup),
|
removeUserFromGroup: expressify(removeUserFromGroup),
|
||||||
removeSelfFromGroup: expressify(removeSelfFromGroup),
|
removeSelfFromGroup: expressify(removeSelfFromGroup),
|
||||||
@@ -232,4 +263,6 @@ export default {
|
|||||||
createAddSeatsSubscriptionChange: expressify(
|
createAddSeatsSubscriptionChange: expressify(
|
||||||
createAddSeatsSubscriptionChange
|
createAddSeatsSubscriptionChange
|
||||||
),
|
),
|
||||||
|
subscriptionUpgradePage: expressify(subscriptionUpgradePage),
|
||||||
|
upgradeSubscription: expressify(upgradeSubscription),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ const SessionManager = require('../Authentication/SessionManager')
|
|||||||
const RecurlyClient = require('./RecurlyClient')
|
const RecurlyClient = require('./RecurlyClient')
|
||||||
const PlansLocator = require('./PlansLocator')
|
const PlansLocator = require('./PlansLocator')
|
||||||
|
|
||||||
|
const PLAN_UPGRADE_MAP = {
|
||||||
|
group_collaborator: 'group_professional',
|
||||||
|
group_collaborator_educational: 'group_professional_educational',
|
||||||
|
}
|
||||||
|
|
||||||
async function removeUserFromGroup(subscriptionId, userIdToRemove) {
|
async function removeUserFromGroup(subscriptionId, userIdToRemove) {
|
||||||
await SubscriptionUpdater.promises.removeUserFromGroup(
|
await SubscriptionUpdater.promises.removeUserFromGroup(
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
@@ -140,12 +145,58 @@ async function createAddSeatsSubscriptionChange(req) {
|
|||||||
return { adding: req.body.adding }
|
return { adding: req.body.adding }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _getUpgradeTargetPlanCodeMaybeThrow(subscription) {
|
||||||
|
if (!Object.keys(PLAN_UPGRADE_MAP).includes(subscription.planCode)) {
|
||||||
|
throw new Error('Not eligible for group plan upgrade')
|
||||||
|
}
|
||||||
|
|
||||||
|
return PLAN_UPGRADE_MAP[subscription.planCode]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _getGroupPlanUpgradeChangeRequest(ownerId) {
|
||||||
|
const olSubscription =
|
||||||
|
await SubscriptionLocator.promises.getUsersSubscription(ownerId)
|
||||||
|
|
||||||
|
const newPlanCode = await _getUpgradeTargetPlanCodeMaybeThrow(olSubscription)
|
||||||
|
|
||||||
|
const recurlySubscription = await RecurlyClient.promises.getSubscription(
|
||||||
|
olSubscription.recurlySubscription_id
|
||||||
|
)
|
||||||
|
return recurlySubscription.getRequestForFlexibleLicensingGroupPlanUpgrade(
|
||||||
|
newPlanCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGroupPlanUpgradePreview(ownerId) {
|
||||||
|
const changeRequest = await _getGroupPlanUpgradeChangeRequest(ownerId)
|
||||||
|
const subscriptionChange =
|
||||||
|
await RecurlyClient.promises.previewSubscriptionChange(changeRequest)
|
||||||
|
const paymentMethod = await RecurlyClient.promises.getPaymentMethod(ownerId)
|
||||||
|
return SubscriptionController.makeChangePreview(
|
||||||
|
{
|
||||||
|
type: 'group-plan-upgrade',
|
||||||
|
prevPlan: {
|
||||||
|
name: subscriptionChange.subscription.planName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subscriptionChange,
|
||||||
|
paymentMethod
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upgradeGroupPlan(ownerId) {
|
||||||
|
const changeRequest = await _getGroupPlanUpgradeChangeRequest(ownerId)
|
||||||
|
await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
removeUserFromGroup: callbackify(removeUserFromGroup),
|
removeUserFromGroup: callbackify(removeUserFromGroup),
|
||||||
replaceUserReferencesInGroups: callbackify(replaceUserReferencesInGroups),
|
replaceUserReferencesInGroups: callbackify(replaceUserReferencesInGroups),
|
||||||
ensureFlexibleLicensingEnabled: callbackify(ensureFlexibleLicensingEnabled),
|
ensureFlexibleLicensingEnabled: callbackify(ensureFlexibleLicensingEnabled),
|
||||||
getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup),
|
getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup),
|
||||||
isUserPartOfGroup: callbackify(isUserPartOfGroup),
|
isUserPartOfGroup: callbackify(isUserPartOfGroup),
|
||||||
|
getGroupPlanUpgradePreview: callbackify(getGroupPlanUpgradePreview),
|
||||||
|
upgradeGroupPlan: callbackify(upgradeGroupPlan),
|
||||||
promises: {
|
promises: {
|
||||||
removeUserFromGroup,
|
removeUserFromGroup,
|
||||||
replaceUserReferencesInGroups,
|
replaceUserReferencesInGroups,
|
||||||
@@ -155,5 +206,7 @@ module.exports = {
|
|||||||
getUsersGroupSubscriptionDetails,
|
getUsersGroupSubscriptionDetails,
|
||||||
previewAddSeatsSubscriptionChange,
|
previewAddSeatsSubscriptionChange,
|
||||||
createAddSeatsSubscriptionChange,
|
createAddSeatsSubscriptionChange,
|
||||||
|
getGroupPlanUpgradePreview,
|
||||||
|
upgradeGroupPlan,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,21 @@ export default {
|
|||||||
SubscriptionGroupController.submitForm
|
SubscriptionGroupController.submitForm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
webRouter.get(
|
||||||
|
'/user/subscription/group/upgrade-subscription',
|
||||||
|
AuthenticationController.requireLogin(),
|
||||||
|
RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
|
||||||
|
SubscriptionGroupController.flexibleLicensingSplitTest,
|
||||||
|
SubscriptionGroupController.subscriptionUpgradePage
|
||||||
|
)
|
||||||
|
|
||||||
|
webRouter.post(
|
||||||
|
'/user/subscription/group/upgrade-subscription',
|
||||||
|
AuthenticationController.requireLogin(),
|
||||||
|
RateLimiterMiddleware.rateLimit(subscriptionRateLimiter),
|
||||||
|
SubscriptionGroupController.upgradeSubscription
|
||||||
|
)
|
||||||
|
|
||||||
// Team invites
|
// Team invites
|
||||||
webRouter.get(
|
webRouter.get(
|
||||||
'/subscription/invites/:token/',
|
'/subscription/invites/:token/',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
extends ../layout-marketing
|
||||||
|
|
||||||
|
block vars
|
||||||
|
- bootstrap5PageStatus = 'enabled' // Enforce BS5 version
|
||||||
|
|
||||||
|
block entrypointVar
|
||||||
|
- entrypoint = 'pages/user/subscription/group-management/upgrade-group-subscription'
|
||||||
|
|
||||||
|
block append meta
|
||||||
|
meta(name="ol-subscriptionChangePreview" data-type="json" content=changePreview)
|
||||||
|
meta(name="ol-totalLicenses", data-type="number", content=totalLicenses)
|
||||||
|
meta(name="ol-groupName", data-type="string", content=groupName)
|
||||||
|
|
||||||
|
block content
|
||||||
|
main.content.content-alt#upgrade-group-subscription-root
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
"add_more_managers": "",
|
"add_more_managers": "",
|
||||||
"add_more_members": "",
|
"add_more_members": "",
|
||||||
"add_more_users": "",
|
"add_more_users": "",
|
||||||
|
"add_more_users_to_my_plan": "",
|
||||||
"add_new_email": "",
|
"add_new_email": "",
|
||||||
"add_ons_are": "",
|
"add_ons_are": "",
|
||||||
"add_or_remove_project_from_tag": "",
|
"add_or_remove_project_from_tag": "",
|
||||||
@@ -108,6 +109,7 @@
|
|||||||
"ai_feedback_there_was_no_code_fix_suggested": "",
|
"ai_feedback_there_was_no_code_fix_suggested": "",
|
||||||
"alignment": "",
|
"alignment": "",
|
||||||
"all_borders": "",
|
"all_borders": "",
|
||||||
|
"all_features_in_group_standard_plus": "",
|
||||||
"all_premium_features": "",
|
"all_premium_features": "",
|
||||||
"all_premium_features_including": "",
|
"all_premium_features_including": "",
|
||||||
"all_projects": "",
|
"all_projects": "",
|
||||||
@@ -159,6 +161,7 @@
|
|||||||
"beta_program_benefits": "",
|
"beta_program_benefits": "",
|
||||||
"beta_program_not_participating": "",
|
"beta_program_not_participating": "",
|
||||||
"better_bibliographies": "",
|
"better_bibliographies": "",
|
||||||
|
"billed_yearly": "",
|
||||||
"binary_history_error": "",
|
"binary_history_error": "",
|
||||||
"blank_project": "",
|
"blank_project": "",
|
||||||
"blocked_filename": "",
|
"blocked_filename": "",
|
||||||
@@ -619,6 +622,7 @@
|
|||||||
"group_libraries": "",
|
"group_libraries": "",
|
||||||
"group_managed_by_group_administrator": "",
|
"group_managed_by_group_administrator": "",
|
||||||
"group_plan_tooltip": "",
|
"group_plan_tooltip": "",
|
||||||
|
"group_plan_upgrade_description": "",
|
||||||
"group_plan_with_name_tooltip": "",
|
"group_plan_with_name_tooltip": "",
|
||||||
"group_sso_configuration_idp_metadata": "",
|
"group_sso_configuration_idp_metadata": "",
|
||||||
"group_sso_configure_service_provider_in_idp": "",
|
"group_sso_configure_service_provider_in_idp": "",
|
||||||
@@ -896,6 +900,7 @@
|
|||||||
"manage_sessions": "",
|
"manage_sessions": "",
|
||||||
"manage_subscription": "",
|
"manage_subscription": "",
|
||||||
"managed": "",
|
"managed": "",
|
||||||
|
"managed_user_accounts": "",
|
||||||
"managed_user_invite_has_been_sent_to_email": "",
|
"managed_user_invite_has_been_sent_to_email": "",
|
||||||
"managed_users": "",
|
"managed_users": "",
|
||||||
"managed_users_explanation": "",
|
"managed_users_explanation": "",
|
||||||
@@ -1076,6 +1081,7 @@
|
|||||||
"pending_additional_licenses": "",
|
"pending_additional_licenses": "",
|
||||||
"pending_addon_cancellation": "",
|
"pending_addon_cancellation": "",
|
||||||
"pending_invite": "",
|
"pending_invite": "",
|
||||||
|
"per_user": "",
|
||||||
"percent_discount_for_groups": "",
|
"percent_discount_for_groups": "",
|
||||||
"percent_is_the_percentage_of_the_line_width": "",
|
"percent_is_the_percentage_of_the_line_width": "",
|
||||||
"permanently_disables_the_preview": "",
|
"permanently_disables_the_preview": "",
|
||||||
@@ -1723,6 +1729,7 @@
|
|||||||
"university": "",
|
"university": "",
|
||||||
"university_school": "",
|
"university_school": "",
|
||||||
"unknown": "",
|
"unknown": "",
|
||||||
|
"unlimited_collaborators_per_project": "",
|
||||||
"unlimited_collabs": "",
|
"unlimited_collabs": "",
|
||||||
"unlimited_projects": "",
|
"unlimited_projects": "",
|
||||||
"unlink": "",
|
"unlink": "",
|
||||||
@@ -1760,10 +1767,12 @@
|
|||||||
"upgrade_for_12x_more_compile_time": "",
|
"upgrade_for_12x_more_compile_time": "",
|
||||||
"upgrade_my_plan": "",
|
"upgrade_my_plan": "",
|
||||||
"upgrade_now": "",
|
"upgrade_now": "",
|
||||||
|
"upgrade_summary": "",
|
||||||
"upgrade_to_add_more_editors": "",
|
"upgrade_to_add_more_editors": "",
|
||||||
"upgrade_to_add_more_editors_and_access_collaboration_features": "",
|
"upgrade_to_add_more_editors_and_access_collaboration_features": "",
|
||||||
"upgrade_to_get_feature": "",
|
"upgrade_to_get_feature": "",
|
||||||
"upgrade_to_track_changes": "",
|
"upgrade_to_track_changes": "",
|
||||||
|
"upgrade_your_subscription": "",
|
||||||
"upload": "",
|
"upload": "",
|
||||||
"upload_from_computer": "",
|
"upload_from_computer": "",
|
||||||
"upload_project": "",
|
"upload_project": "",
|
||||||
@@ -1784,6 +1793,7 @@
|
|||||||
"user_first_name_attribute": "",
|
"user_first_name_attribute": "",
|
||||||
"user_last_name_attribute": "",
|
"user_last_name_attribute": "",
|
||||||
"user_sessions": "",
|
"user_sessions": "",
|
||||||
|
"users": "",
|
||||||
"using_latex": "",
|
"using_latex": "",
|
||||||
"using_premium_features": "",
|
"using_premium_features": "",
|
||||||
"using_the_overleaf_editor": "",
|
"using_the_overleaf_editor": "",
|
||||||
@@ -1830,6 +1840,7 @@
|
|||||||
"we_logged_you_in": "",
|
"we_logged_you_in": "",
|
||||||
"we_sent_new_code": "",
|
"we_sent_new_code": "",
|
||||||
"we_will_charge_you_now_for_the_cost_of_your_additional_users_based_on_remaining_months": "",
|
"we_will_charge_you_now_for_the_cost_of_your_additional_users_based_on_remaining_months": "",
|
||||||
|
"we_will_charge_you_now_for_your_new_plan_based_on_the_remaining_months_of_your_current_subscription": "",
|
||||||
"webinars": "",
|
"webinars": "",
|
||||||
"website_status": "",
|
"website_status": "",
|
||||||
"wed_love_you_to_stay": "",
|
"wed_love_you_to_stay": "",
|
||||||
@@ -1901,6 +1912,7 @@
|
|||||||
"you_have_been_invited_to_transfer_management_of_your_account_to": "",
|
"you_have_been_invited_to_transfer_management_of_your_account_to": "",
|
||||||
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "",
|
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "",
|
||||||
"you_have_x_users_and_your_plan_supports_up_to_y": "",
|
"you_have_x_users_and_your_plan_supports_up_to_y": "",
|
||||||
|
"you_have_x_users_on_your_subscription": "",
|
||||||
"you_need_to_configure_your_sso_settings": "",
|
"you_need_to_configure_your_sso_settings": "",
|
||||||
"you_will_be_able_to_reassign_subscription": "",
|
"you_will_be_able_to_reassign_subscription": "",
|
||||||
"youll_get_best_results_in_visual_but_can_be_used_in_source": "",
|
"youll_get_best_results_in_visual_but_can_be_used_in_source": "",
|
||||||
@@ -1949,6 +1961,7 @@
|
|||||||
"youve_added_x_more_users_to_your_subscription_invite_people": "",
|
"youve_added_x_more_users_to_your_subscription_invite_people": "",
|
||||||
"youve_lost_edit_access": "",
|
"youve_lost_edit_access": "",
|
||||||
"youve_unlinked_all_users": "",
|
"youve_unlinked_all_users": "",
|
||||||
|
"youve_upgraded_your_plan": "",
|
||||||
"zoom_in": "",
|
"zoom_in": "",
|
||||||
"zoom_out": "",
|
"zoom_out": "",
|
||||||
"zoom_to": "",
|
"zoom_to": "",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import classnames from 'classnames'
|
|||||||
type RequestStatusProps = {
|
type RequestStatusProps = {
|
||||||
icon: string
|
icon: string
|
||||||
title: string
|
title: string
|
||||||
content: React.ReactNode
|
content?: React.ReactNode
|
||||||
variant?: 'primary' | 'danger'
|
variant?: 'primary' | 'danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,9 @@ function RequestStatus({ icon, title, content, variant }: RequestStatusProps) {
|
|||||||
<h3 className="mb-0 fw-bold" data-testid="title">
|
<h3 className="mb-0 fw-bold" data-testid="title">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="card-description-secondary">{content}</div>
|
{content && (
|
||||||
|
<div className="card-description-secondary">{content}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Button variant="secondary" href="/user/subscription">
|
<Button variant="secondary" href="/user/subscription">
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
import getMeta from '@/utils/meta'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Card, Row, Col } from 'react-bootstrap-5'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { formatCurrencyLocalized } from '@/shared/utils/currency'
|
||||||
|
|
||||||
|
const LICENSE_ADD_ON = 'additional-license'
|
||||||
|
|
||||||
|
function UpgradeSubscriptionPlanDetails() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const preview = getMeta('ol-subscriptionChangePreview')
|
||||||
|
|
||||||
|
const licenseUnitPrice = useMemo(
|
||||||
|
() =>
|
||||||
|
preview.nextInvoice.addOns.filter(
|
||||||
|
addOn => addOn.code === LICENSE_ADD_ON
|
||||||
|
)[0].unitAmount,
|
||||||
|
[preview]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="group-subscription-upgrade-features card-description-secondary border-1"
|
||||||
|
border="light"
|
||||||
|
>
|
||||||
|
<Card.Body className="d-grid gap-3 p-3">
|
||||||
|
<b>{preview.nextInvoice.plan.name}</b>
|
||||||
|
<Row xs="auto" className="gx-2">
|
||||||
|
<Col>
|
||||||
|
<span className="per-user-price">
|
||||||
|
<b>
|
||||||
|
{formatCurrencyLocalized(
|
||||||
|
licenseUnitPrice,
|
||||||
|
preview.currency,
|
||||||
|
getMeta('ol-i18n')?.currentLangCode ?? 'en',
|
||||||
|
true
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</Col>
|
||||||
|
<Col className="d-flex flex-column justify-content-center">
|
||||||
|
<div className="per-user-price-text">{t('per_user')}</div>
|
||||||
|
<div className="per-user-price-text">{t('billed_yearly')}</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div className="feature-list-item">
|
||||||
|
<b>{t('all_features_in_group_standard_plus')}</b>
|
||||||
|
</div>
|
||||||
|
<div className="ps-2 feature-list-item">
|
||||||
|
<MaterialIcon type="check" className="me-1" />
|
||||||
|
{t('unlimited_collaborators_per_project')}
|
||||||
|
</div>
|
||||||
|
<div className="ps-2 feature-list-item">
|
||||||
|
<MaterialIcon type="check" className="me-1" />
|
||||||
|
{t('sso')}
|
||||||
|
</div>
|
||||||
|
<div className="ps-2 feature-list-item">
|
||||||
|
<MaterialIcon type="check" className="me-1" />
|
||||||
|
{t('managed_user_accounts')}
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpgradeSubscriptionPlanDetails
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Card, ListGroup } from 'react-bootstrap-5'
|
||||||
|
import { formatCurrencyLocalized } from '@/shared/utils/currency'
|
||||||
|
import { formatTime } from '@/features/utils/format-date'
|
||||||
|
import {
|
||||||
|
GroupPlanUpgrade,
|
||||||
|
SubscriptionChangePreview,
|
||||||
|
} from '../../../../../../types/subscription/subscription-change-preview'
|
||||||
|
import { MergeAndOverride } from '../../../../../../types/utils'
|
||||||
|
import getMeta from '@/utils/meta'
|
||||||
|
|
||||||
|
export type SubscriptionChange = MergeAndOverride<
|
||||||
|
SubscriptionChangePreview,
|
||||||
|
{ change: GroupPlanUpgrade }
|
||||||
|
>
|
||||||
|
|
||||||
|
type UpgradeSummaryProps = {
|
||||||
|
subscriptionChange: SubscriptionChange
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const totalLicenses = getMeta('ol-totalLicenses')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="card-gray card-description-secondary">
|
||||||
|
<Card.Body className="d-grid gap-2 p-3">
|
||||||
|
<div>
|
||||||
|
<div className="fw-bold">{t('upgrade_summary')}</div>
|
||||||
|
{t('you_have_x_users_on_your_subscription', {
|
||||||
|
groupSize: totalLicenses,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ListGroup>
|
||||||
|
<ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
|
||||||
|
<span className="me-auto">
|
||||||
|
{subscriptionChange.nextInvoice.plan.name} x {totalLicenses}{' '}
|
||||||
|
{t('users')}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{formatCurrencyLocalized(
|
||||||
|
subscriptionChange.immediateCharge.subtotal,
|
||||||
|
subscriptionChange.currency
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
|
||||||
|
<span className="me-auto">{t('sales_tax')}</span>
|
||||||
|
<span>
|
||||||
|
{formatCurrencyLocalized(
|
||||||
|
subscriptionChange.immediateCharge.tax,
|
||||||
|
subscriptionChange.currency
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
|
||||||
|
<strong className="me-auto">{t('total_due_today')}</strong>
|
||||||
|
<strong>
|
||||||
|
{formatCurrencyLocalized(
|
||||||
|
subscriptionChange.immediateCharge.total,
|
||||||
|
subscriptionChange.currency
|
||||||
|
)}
|
||||||
|
</strong>
|
||||||
|
</ListGroup.Item>
|
||||||
|
</ListGroup>
|
||||||
|
<hr className="m-0" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
'we_will_charge_you_now_for_your_new_plan_based_on_the_remaining_months_of_your_current_subscription'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{t('after_that_well_bill_you_x_annually_on_date_unless_you_cancel', {
|
||||||
|
subtotal: formatCurrencyLocalized(
|
||||||
|
subscriptionChange.nextInvoice.subtotal,
|
||||||
|
subscriptionChange.currency
|
||||||
|
),
|
||||||
|
date: formatTime(subscriptionChange.nextInvoice.date, 'MMMM D'),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpgradeSummary
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
import getMeta from '@/utils/meta'
|
||||||
|
import { postJSON } from '@/infrastructure/fetch-json'
|
||||||
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
|
import { Card, Row, Col } from 'react-bootstrap-5'
|
||||||
|
import IconButton from '@/features/ui/components/bootstrap-5/icon-button'
|
||||||
|
import Button from '@/features/ui/components/bootstrap-5/button'
|
||||||
|
import UpgradeSubscriptionPlanDetails from './upgrade-subscription-plan-details'
|
||||||
|
import useAsync from '@/shared/hooks/use-async'
|
||||||
|
import RequestStatus from '../request-status'
|
||||||
|
import UpgradeSummary, {
|
||||||
|
SubscriptionChange,
|
||||||
|
} from './upgrade-subscription-upgrade-summary'
|
||||||
|
|
||||||
|
function UpgradeSubscription() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const groupName = getMeta('ol-groupName')
|
||||||
|
const preview = getMeta('ol-subscriptionChangePreview') as SubscriptionChange
|
||||||
|
const { isError, runAsync, isSuccess, isLoading } = useAsync()
|
||||||
|
const onSubmit = () => {
|
||||||
|
runAsync(postJSON('/user/subscription/group/upgrade-subscription'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
return (
|
||||||
|
<RequestStatus
|
||||||
|
variant="primary"
|
||||||
|
icon="check_circle"
|
||||||
|
title={t('youve_upgraded_your_plan')}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<RequestStatus
|
||||||
|
variant="danger"
|
||||||
|
icon="error"
|
||||||
|
title={t('something_went_wrong')}
|
||||||
|
content={
|
||||||
|
<Trans
|
||||||
|
i18nKey="it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch"
|
||||||
|
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
|
||||||
|
components={[<a href="/contact" />]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<Row>
|
||||||
|
<Col xl={{ span: 8, offset: 2 }}>
|
||||||
|
<div className="group-heading">
|
||||||
|
<IconButton
|
||||||
|
variant="ghost"
|
||||||
|
href="/user/subscription"
|
||||||
|
size="lg"
|
||||||
|
icon="arrow_back"
|
||||||
|
accessibilityLabel={t('back_to_subscription')}
|
||||||
|
/>
|
||||||
|
<h2>{groupName || t('group_subscription')}</h2>
|
||||||
|
</div>
|
||||||
|
<Card className="card-description-secondary group-subscription-upgrade-card">
|
||||||
|
<Card.Body className="d-grid gap-2">
|
||||||
|
<b className="title">{t('upgrade_your_subscription')}</b>
|
||||||
|
<p>
|
||||||
|
<Trans
|
||||||
|
i18nKey="group_plan_upgrade_description"
|
||||||
|
values={{
|
||||||
|
currentPlan: preview.change.prevPlan.name,
|
||||||
|
nextPlan: preview.nextInvoice.plan.name,
|
||||||
|
}}
|
||||||
|
shouldUnescape
|
||||||
|
tOptions={{ interpolation: { escapeValue: true } }}
|
||||||
|
components={[
|
||||||
|
// eslint-disable-next-line react/jsx-key
|
||||||
|
<strong />,
|
||||||
|
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
||||||
|
<a href="/contact" />,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<Row className="mb-2">
|
||||||
|
<Col md={{ span: 6 }} className="mb-2">
|
||||||
|
<UpgradeSubscriptionPlanDetails />
|
||||||
|
</Col>
|
||||||
|
<Col md={{ span: 6 }}>
|
||||||
|
<UpgradeSummary subscriptionChange={preview} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div className="d-flex align-items-center justify-content-end gap-2">
|
||||||
|
<a
|
||||||
|
href="/user/subscription/group/add-users"
|
||||||
|
className="me-auto"
|
||||||
|
>
|
||||||
|
{t('add_more_users_to_my_plan')}
|
||||||
|
</a>
|
||||||
|
<Button
|
||||||
|
href="/user/subscription"
|
||||||
|
variant="secondary"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={onSubmit}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
{t('upgrade')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpgradeSubscription
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
import '../base'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import UpgradeSubscription from '@/features/group-management/components/upgrade-subscription/upgrade-subscription'
|
||||||
|
|
||||||
|
const element = document.getElementById('upgrade-group-subscription-root')
|
||||||
|
if (element) {
|
||||||
|
ReactDOM.render(<UpgradeSubscription />, element)
|
||||||
|
}
|
||||||
@@ -382,3 +382,31 @@
|
|||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group-subscription-upgrade-features {
|
||||||
|
.material-symbols {
|
||||||
|
@include body-sm;
|
||||||
|
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.per-user-price {
|
||||||
|
@include heading-lg;
|
||||||
|
|
||||||
|
color: var(--content-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.per-user-price-text {
|
||||||
|
@include body-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list-item {
|
||||||
|
@include body-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-subscription-upgrade-card {
|
||||||
|
.title {
|
||||||
|
@include body-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"add_more_managers": "Add more managers",
|
"add_more_managers": "Add more managers",
|
||||||
"add_more_members": "Add more members",
|
"add_more_members": "Add more members",
|
||||||
"add_more_users": "Add more users",
|
"add_more_users": "Add more users",
|
||||||
|
"add_more_users_to_my_plan": "Add more users to my plan",
|
||||||
"add_new_email": "Add new email",
|
"add_new_email": "Add new email",
|
||||||
"add_ons_are": "<strong>Add-ons:</strong> __addOnName__",
|
"add_ons_are": "<strong>Add-ons:</strong> __addOnName__",
|
||||||
"add_or_remove_project_from_tag": "Add or remove project from tag __tagName__",
|
"add_or_remove_project_from_tag": "Add or remove project from tag __tagName__",
|
||||||
@@ -128,6 +129,7 @@
|
|||||||
"alignment": "Alignment",
|
"alignment": "Alignment",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"all_borders": "All borders",
|
"all_borders": "All borders",
|
||||||
|
"all_features_in_group_standard_plus": "All features in Group Standard, plus:",
|
||||||
"all_our_group_plans_offer_educational_discount": "All of our <0>group plans</0> offer an <1>educational discount</1> for students and faculty",
|
"all_our_group_plans_offer_educational_discount": "All of our <0>group plans</0> offer an <1>educational discount</1> for students and faculty",
|
||||||
"all_premium_features": "All premium features",
|
"all_premium_features": "All premium features",
|
||||||
"all_premium_features_including": "All premium features, including:",
|
"all_premium_features_including": "All premium features, including:",
|
||||||
@@ -213,6 +215,7 @@
|
|||||||
"beta_program_opt_out_action": "Opt-Out of Beta Program",
|
"beta_program_opt_out_action": "Opt-Out of Beta Program",
|
||||||
"better_bibliographies": "Better bibliographies",
|
"better_bibliographies": "Better bibliographies",
|
||||||
"bibliographies": "Bibliographies",
|
"bibliographies": "Bibliographies",
|
||||||
|
"billed_yearly": "billed yearly",
|
||||||
"billing_period_sentence_case": "Billing period",
|
"billing_period_sentence_case": "Billing period",
|
||||||
"binary_history_error": "Preview not available for this file type",
|
"binary_history_error": "Preview not available for this file type",
|
||||||
"blank_project": "Blank Project",
|
"blank_project": "Blank Project",
|
||||||
@@ -888,6 +891,7 @@
|
|||||||
"group_members_get_access_to_info": "These features are available only to group members (subscribers).",
|
"group_members_get_access_to_info": "These features are available only to group members (subscribers).",
|
||||||
"group_plan_admins_can_easily_add_and_remove_users_from_a_group": "Group plan admins can easily add and remove users from a group. For site-wide plans, users are automatically upgraded when they register or add their email address to Overleaf (domain-based enrollment or SSO).",
|
"group_plan_admins_can_easily_add_and_remove_users_from_a_group": "Group plan admins can easily add and remove users from a group. For site-wide plans, users are automatically upgraded when they register or add their email address to Overleaf (domain-based enrollment or SSO).",
|
||||||
"group_plan_tooltip": "You are on the __plan__ plan as a member of a group subscription. Click to find out how to make the most of your Overleaf premium features.",
|
"group_plan_tooltip": "You are on the __plan__ plan as a member of a group subscription. Click to find out how to make the most of your Overleaf premium features.",
|
||||||
|
"group_plan_upgrade_description": "You’re on the <0>__currentPlan__</0> plan and you’re upgrading to the <0>__nextPlan__</0> plan. If you’re interested in a site-wide Overleaf Commons plan please <1>get in touch</1>.",
|
||||||
"group_plan_with_name_tooltip": "You are on the __plan__ plan as a member of a group subscription, __groupName__. Click to find out how to make the most of your Overleaf premium features.",
|
"group_plan_with_name_tooltip": "You are on the __plan__ plan as a member of a group subscription, __groupName__. Click to find out how to make the most of your Overleaf premium features.",
|
||||||
"group_plans": "Group Plans",
|
"group_plans": "Group Plans",
|
||||||
"group_professional": "Group Professional",
|
"group_professional": "Group Professional",
|
||||||
@@ -2392,10 +2396,12 @@
|
|||||||
"upgrade_for_12x_more_compile_time": "Upgrade to get 12x more compile time",
|
"upgrade_for_12x_more_compile_time": "Upgrade to get 12x more compile time",
|
||||||
"upgrade_my_plan": "Upgrade my plan",
|
"upgrade_my_plan": "Upgrade my plan",
|
||||||
"upgrade_now": "Upgrade now",
|
"upgrade_now": "Upgrade now",
|
||||||
|
"upgrade_summary": "Upgrade summary",
|
||||||
"upgrade_to_add_more_editors": "Upgrade to add more editors to your project",
|
"upgrade_to_add_more_editors": "Upgrade to add more editors to your project",
|
||||||
"upgrade_to_add_more_editors_and_access_collaboration_features": "Upgrade to add more editors and access collaboration features like track changes and full project history.",
|
"upgrade_to_add_more_editors_and_access_collaboration_features": "Upgrade to add more editors and access collaboration features like track changes and full project history.",
|
||||||
"upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
|
"upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
|
||||||
"upgrade_to_track_changes": "Upgrade to track changes",
|
"upgrade_to_track_changes": "Upgrade to track changes",
|
||||||
|
"upgrade_your_subscription": "Upgrade your subscription",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"upload_failed": "Upload failed",
|
"upload_failed": "Upload failed",
|
||||||
"upload_from_computer": "Upload from computer",
|
"upload_from_computer": "Upload from computer",
|
||||||
@@ -2427,6 +2433,7 @@
|
|||||||
"user_not_found": "User not found",
|
"user_not_found": "User not found",
|
||||||
"user_sessions": "User Sessions",
|
"user_sessions": "User Sessions",
|
||||||
"user_wants_you_to_see_project": "__username__ would like you to join __projectname__",
|
"user_wants_you_to_see_project": "__username__ would like you to join __projectname__",
|
||||||
|
"users": "users",
|
||||||
"using_latex": "Using LaTeX",
|
"using_latex": "Using LaTeX",
|
||||||
"using_premium_features": "Using premium features",
|
"using_premium_features": "Using premium features",
|
||||||
"using_the_overleaf_editor": "Using the __appName__ Editor",
|
"using_the_overleaf_editor": "Using the __appName__ Editor",
|
||||||
@@ -2477,6 +2484,7 @@
|
|||||||
"we_may_also_contact_you_from_time_to_time_by_email_with_a_survey": "<0>We may also contact you</0> from time to time by email with a survey, or to see if you would like to participate in other user research initiatives",
|
"we_may_also_contact_you_from_time_to_time_by_email_with_a_survey": "<0>We may also contact you</0> from time to time by email with a survey, or to see if you would like to participate in other user research initiatives",
|
||||||
"we_sent_new_code": "We’ve sent a new code. If it doesn’t arrive, make sure to check your spam and any promotions folders.",
|
"we_sent_new_code": "We’ve sent a new code. If it doesn’t arrive, make sure to check your spam and any promotions folders.",
|
||||||
"we_will_charge_you_now_for_the_cost_of_your_additional_users_based_on_remaining_months": "We’ll charge you now for the cost of your additional users based on the remaining months of your current subscription.",
|
"we_will_charge_you_now_for_the_cost_of_your_additional_users_based_on_remaining_months": "We’ll charge you now for the cost of your additional users based on the remaining months of your current subscription.",
|
||||||
|
"we_will_charge_you_now_for_your_new_plan_based_on_the_remaining_months_of_your_current_subscription": "We’ll charge you now for your new plan based on the remaining months of your current subscription.",
|
||||||
"webinars": "Webinars",
|
"webinars": "Webinars",
|
||||||
"website_status": "Website status",
|
"website_status": "Website status",
|
||||||
"wed_love_you_to_stay": "We’d love you to stay",
|
"wed_love_you_to_stay": "We’d love you to stay",
|
||||||
@@ -2565,6 +2573,7 @@
|
|||||||
"you_have_been_invited_to_transfer_management_of_your_account_to": "You have been invited to transfer management of your account to __groupName__.",
|
"you_have_been_invited_to_transfer_management_of_your_account_to": "You have been invited to transfer management of your account to __groupName__.",
|
||||||
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.",
|
"you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.",
|
||||||
"you_have_x_users_and_your_plan_supports_up_to_y": "You have __addedUsersSize__ users and your plan supports up to __groupSize__.",
|
"you_have_x_users_and_your_plan_supports_up_to_y": "You have __addedUsersSize__ users and your plan supports up to __groupSize__.",
|
||||||
|
"you_have_x_users_on_your_subscription": "You have __groupSize__ users on your subscription.",
|
||||||
"you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO",
|
"you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO",
|
||||||
"you_plus_1": "You + 1",
|
"you_plus_1": "You + 1",
|
||||||
"you_plus_10": "You + 10",
|
"you_plus_10": "You + 10",
|
||||||
@@ -2622,6 +2631,7 @@
|
|||||||
"youve_added_x_more_users_to_your_subscription_invite_people": "You’ve added __users__ more users to your subscription. <0>Invite people</0>.",
|
"youve_added_x_more_users_to_your_subscription_invite_people": "You’ve added __users__ more users to your subscription. <0>Invite people</0>.",
|
||||||
"youve_lost_edit_access": "You’ve lost edit access",
|
"youve_lost_edit_access": "You’ve lost edit access",
|
||||||
"youve_unlinked_all_users": "You’ve unlinked all users",
|
"youve_unlinked_all_users": "You’ve unlinked all users",
|
||||||
|
"youve_upgraded_your_plan": "You’ve upgraded your plan!",
|
||||||
"zh-CN": "Chinese",
|
"zh-CN": "Chinese",
|
||||||
"zip_contents_too_large": "Zip contents too large",
|
"zip_contents_too_large": "Zip contents too large",
|
||||||
"zoom_in": "Zoom in",
|
"zoom_in": "Zoom in",
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ describe('SubscriptionGroupController', function () {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.RecurlyClient = {}
|
||||||
|
|
||||||
|
this.SubscriptionController = {}
|
||||||
|
|
||||||
|
this.SubscriptionModel = { Subscription: {} }
|
||||||
|
|
||||||
this.Controller = await esmock.strict(modulePath, {
|
this.Controller = await esmock.strict(modulePath, {
|
||||||
'../../../../app/src/Features/Subscription/SubscriptionGroupHandler':
|
'../../../../app/src/Features/Subscription/SubscriptionGroupHandler':
|
||||||
this.SubscriptionGroupHandler,
|
this.SubscriptionGroupHandler,
|
||||||
@@ -94,6 +100,11 @@ describe('SubscriptionGroupController', function () {
|
|||||||
(this.ErrorController = {
|
(this.ErrorController = {
|
||||||
notFound: sinon.stub(),
|
notFound: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
|
'../../../../app/src/Features/Subscription/SubscriptionController':
|
||||||
|
this.SubscriptionController,
|
||||||
|
'../../../../app/src/Features/Subscription/RecurlyClient':
|
||||||
|
this.RecurlyClient,
|
||||||
|
'../../../../app/src/models/Subscription': this.SubscriptionModel,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export type SubscriptionChangeDescription =
|
|||||||
| AddOnPurchase
|
| AddOnPurchase
|
||||||
| AddOnUpdate
|
| AddOnUpdate
|
||||||
| PremiumSubscription
|
| PremiumSubscription
|
||||||
|
| GroupPlanUpgrade
|
||||||
|
|
||||||
export type AddOnPurchase = {
|
export type AddOnPurchase = {
|
||||||
type: 'add-on-purchase'
|
type: 'add-on-purchase'
|
||||||
@@ -51,6 +52,13 @@ export type AddOnUpdate = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GroupPlanUpgrade = {
|
||||||
|
type: 'group-plan-upgrade'
|
||||||
|
prevPlan: {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type PremiumSubscription = {
|
type PremiumSubscription = {
|
||||||
type: 'premium-subscription'
|
type: 'premium-subscription'
|
||||||
plan: {
|
plan: {
|
||||||
|
|||||||
Reference in New Issue
Block a user