From 150cf217106d14f857003dd9d5f57689e9d43876 Mon Sep 17 00:00:00 2001 From: Alexandre Bourdin Date: Mon, 5 Jun 2023 13:03:06 +0300 Subject: [PATCH] Merge pull request #13311 from overleaf/ab-tear-down-subscription-pages-react [web] Tear down subscription-pages-react test and remove Angular code GitOrigin-RevId: 3cf906e476ffa52a058ccb4e4acbb89a657bd021 --- .../Subscription/SubscriptionController.js | 226 +---- .../UserMembershipController.js | 161 +--- .../subscriptions/canceled-subscription.pug | 15 - .../web/app/views/subscriptions/dashboard.pug | 68 -- .../dashboard/_change_plans_mixins.pug | 28 - .../dashboard/_group_memberships.pug | 32 - .../dashboard/_institution_memberships.pug | 10 - .../dashboard/_managed_groups.pug | 24 - .../dashboard/_managed_institutions.pug | 24 - .../dashboard/_managed_publishers.pug | 16 - .../dashboard/_personal_subscription.pug | 7 - .../_personal_subscription_custom.pug | 6 - .../_personal_subscription_recurly.pug | 159 ---- ...rsonal_subscription_recurly_sync_email.pug | 18 - .../dashboard/_team_name_mixin.pug | 9 - .../dashboard/_v1_subscription_status.pug | 62 -- .../app/views/subscriptions/new-refreshed.pug | 364 -------- .../subscriptions/successful-subscription.pug | 40 - .../web/app/views/user_membership/index.pug | 119 --- services/web/frontend/js/main.js | 4 - .../web/frontend/js/main/new-subscription.js | 793 ------------------ .../js/main/subscription-dashboard.js | 451 ---------- .../main/subscription/upgrade-subscription.js | 13 - .../web/frontend/js/main/user-membership.js | 126 --- services/web/locales/en.json | 2 - .../SubscriptionControllerTests.js | 19 +- .../UserMembershipControllerTests.js | 21 +- 27 files changed, 50 insertions(+), 2767 deletions(-) delete mode 100644 services/web/app/views/subscriptions/canceled-subscription.pug delete mode 100644 services/web/app/views/subscriptions/dashboard.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_change_plans_mixins.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_group_memberships.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_institution_memberships.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_managed_groups.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_managed_institutions.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_managed_publishers.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_personal_subscription.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_personal_subscription_custom.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly_sync_email.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_team_name_mixin.pug delete mode 100644 services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug delete mode 100644 services/web/app/views/subscriptions/new-refreshed.pug delete mode 100644 services/web/app/views/subscriptions/successful-subscription.pug delete mode 100644 services/web/app/views/user_membership/index.pug delete mode 100644 services/web/frontend/js/main/new-subscription.js delete mode 100644 services/web/frontend/js/main/subscription-dashboard.js delete mode 100644 services/web/frontend/js/main/subscription/upgrade-subscription.js delete mode 100644 services/web/frontend/js/main/user-membership.js diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 4315462be7..f2d7b0e4d3 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -129,34 +129,12 @@ async function plansPage(req, res) { }) } -async function paymentPage(req, res) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - // get to show the recurly.js page - if (assignment.variant === 'active') { - await _paymentReactPage(req, res) - } else { - await _paymentAngularPage(req, res) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _paymentAngularPage(req, res) - } -} - /** * @param {import("express").Request} req * @param {import("express").Response} res * @returns {Promise} */ -async function _paymentReactPage(req, res) { +async function paymentPage(req, res) { const user = SessionManager.getSessionUser(req.session) const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode) if (!plan) { @@ -211,82 +189,6 @@ async function _paymentReactPage(req, res) { } } -async function _paymentAngularPage(req, res) { - const user = SessionManager.getSessionUser(req.session) - const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode) - if (!plan) { - return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found') - } - const hasSubscription = - await LimitationsManager.promises.userHasV1OrV2Subscription(user) - if (hasSubscription) { - res.redirect('/user/subscription?hasSubscription=true') - } else { - // LimitationsManager.userHasV2Subscription only checks Mongo. Double check with - // Recurly as well at this point (we don't do this most places for speed). - const valid = - await SubscriptionHandler.promises.validateNoSubscriptionInRecurly( - user._id - ) - if (!valid) { - res.redirect('/user/subscription?hasSubscription=true') - } else { - let currency = null - if (req.query.currency) { - const queryCurrency = req.query.currency.toUpperCase() - if (GeoIpLookup.isValidCurrencyParam(queryCurrency)) { - currency = queryCurrency - } - } - const { currencyCode: recommendedCurrency, countryCode } = - await GeoIpLookup.promises.getCurrencyCode(req.query?.ip || req.ip) - if (recommendedCurrency && currency == null) { - currency = recommendedCurrency - } - - await SplitTestHandler.promises.getAssignment( - req, - res, - 'student-check-modal' - ) - - res.render('subscriptions/new-refreshed', { - title: 'subscribe', - currency, - countryCode, - plan, - recurlyConfig: JSON.stringify({ - currency, - subdomain: Settings.apis.recurly.subdomain, - }), - showCouponField: !!req.query.scf, - showVatField: !!req.query.svf, - }) - } - } -} - -async function userSubscriptionPage(req, res) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _userSubscriptionReactPage(req, res) - } else { - await _userSubscriptionAngularPage(req, res) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _userSubscriptionAngularPage(req, res) - } -} - function formatGroupPlansDataForDash() { return { plans: [...groupPlanModalOptions.plan_codes], @@ -301,7 +203,7 @@ function formatGroupPlansDataForDash() { * @param {import("express").Response} res * @returns {Promise} */ -async function _userSubscriptionReactPage(req, res) { +async function userSubscriptionPage(req, res) { const user = SessionManager.getSessionUser(req.session) const results = await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( @@ -356,58 +258,6 @@ async function _userSubscriptionReactPage(req, res) { res.render('subscriptions/dashboard-react', data) } -async function _userSubscriptionAngularPage(req, res) { - const user = SessionManager.getSessionUser(req.session) - const results = - await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( - user - ) - const { - personalSubscription, - memberGroupSubscriptions, - managedGroupSubscriptions, - currentInstitutionsWithLicence, - managedInstitutions, - managedPublishers, - v1SubscriptionStatus, - } = results - const hasSubscription = - await LimitationsManager.promises.userHasV1OrV2Subscription(user) - const fromPlansPage = req.query.hasSubscription - const plans = SubscriptionViewModelBuilder.buildPlansList( - personalSubscription ? personalSubscription.plan : undefined - ) - - AnalyticsManager.recordEventForSession(req.session, 'subscription-page-view') - - const cancelButtonAssignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-cancel-button' - ) - - const cancelButtonNewCopy = cancelButtonAssignment?.variant === 'new-copy' - - const data = { - title: 'your_subscription', - plans, - groupPlans: GroupPlansData, - user, - hasSubscription, - fromPlansPage, - personalSubscription, - memberGroupSubscriptions, - managedGroupSubscriptions, - managedInstitutions, - managedPublishers, - v1SubscriptionStatus, - currentInstitutionsWithLicence, - groupPlanModalOptions, - cancelButtonNewCopy, - } - res.render('subscriptions/dashboard', data) -} - async function interstitialPaymentPage(req, res) { const user = SessionManager.getSessionUser(req.session) const { recommendedCurrency, countryCode, geoPricingTestVariant } = @@ -515,33 +365,12 @@ async function createSubscription(req, res) { } } -async function successfulSubscription(req, res) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _successfulSubscriptionReact(req, res) - } else { - await _successfulSubscriptionAngular(req, res) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _successfulSubscriptionAngular(req, res) - } -} - /** * @param {import("express").Request} req * @param {import("express").Response} res * @returns {Promise} */ -async function _successfulSubscriptionReact(req, res) { +async function successfulSubscription(req, res) { const user = SessionManager.getSessionUser(req.session) const { personalSubscription } = await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( @@ -561,26 +390,6 @@ async function _successfulSubscriptionReact(req, res) { } } -async function _successfulSubscriptionAngular(req, res) { - const user = SessionManager.getSessionUser(req.session) - const { personalSubscription } = - await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( - user - ) - - const postCheckoutRedirect = req.session?.postCheckoutRedirect - - if (!personalSubscription) { - res.redirect('/user/subscription/plans') - } else { - res.render('subscriptions/successful-subscription', { - title: 'thank_you', - personalSubscription, - postCheckoutRedirect, - }) - } -} - function cancelSubscription(req, res, next) { const user = SessionManager.getSessionUser(req.session) logger.debug({ userId: user._id }, 'canceling subscription') @@ -597,45 +406,18 @@ function cancelSubscription(req, res, next) { }) } -async function canceledSubscription(req, res, next) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _canceledSubscriptionReact(req, res, next) - } else { - await _canceledSubscriptionAngular(req, res, next) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _canceledSubscriptionAngular(req, res, next) - } -} - /** * @param {import("express").Request} req * @param {import("express").Response} res * @param {import("express").NextFunction} next * @returns {Promise} */ -function _canceledSubscriptionReact(req, res, next) { +function canceledSubscription(req, res, next) { return res.render('subscriptions/canceled-subscription-react', { title: 'subscription_canceled', }) } -function _canceledSubscriptionAngular(req, res, next) { - return res.render('subscriptions/canceled-subscription', { - title: 'subscription_canceled', - }) -} - function cancelV1Subscription(req, res, next) { const userId = SessionManager.getLoggedInUserId(req.session) logger.debug({ userId }, 'canceling v1 subscription') diff --git a/services/web/app/src/Features/UserMembership/UserMembershipController.js b/services/web/app/src/Features/UserMembership/UserMembershipController.js index f0b700e7a7..7d1d45f1d6 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipController.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipController.js @@ -16,110 +16,9 @@ const Errors = require('../Errors/Errors') const EmailHelper = require('../Helpers/EmailHelper') const { csvAttachment } = require('../../infrastructure/Response') const { UserIsManagerError } = require('./UserMembershipErrors') -const SplitTestHandler = require('../SplitTests/SplitTestHandler') const CSVParser = require('json2csv').Parser -const logger = require('@overleaf/logger') async function manageGroupMembers(req, res, next) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _manageGroupMembersReact(req, res, next) - } else { - await _indexAngular(req, res, next) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _indexAngular(req, res, next) - } -} - -async function manageGroupManagers(req, res, next) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _renderManagersPage( - req, - res, - next, - 'user_membership/group-managers-react' - ) - } else { - await _indexAngular(req, res, next) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _indexAngular(req, res, next) - } -} - -async function manageInstitutionManagers(req, res, next) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _renderManagersPage( - req, - res, - next, - 'user_membership/institution-managers-react' - ) - } else { - await _indexAngular(req, res, next) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _indexAngular(req, res, next) - } -} - -async function managePublisherManagers(req, res, next) { - try { - const assignment = await SplitTestHandler.promises.getAssignment( - req, - res, - 'subscription-pages-react' - ) - if (assignment.variant === 'active') { - await _renderManagersPage( - req, - res, - next, - 'user_membership/publisher-managers-react' - ) - } else { - await _indexAngular(req, res, next) - } - } catch (error) { - logger.warn( - { err: error }, - 'failed to get "subscription-pages-react" split test assignment' - ) - await _indexAngular(req, res, next) - } -} - -async function _manageGroupMembersReact(req, res, next) { const { entity, entityConfig } = req return entity.fetchV1Data(function (error, entity) { if (error != null) { @@ -149,6 +48,33 @@ async function _manageGroupMembersReact(req, res, next) { }) } +async function manageGroupManagers(req, res, next) { + await _renderManagersPage( + req, + res, + next, + 'user_membership/group-managers-react' + ) +} + +async function manageInstitutionManagers(req, res, next) { + await _renderManagersPage( + req, + res, + next, + 'user_membership/institution-managers-react' + ) +} + +async function managePublisherManagers(req, res, next) { + await _renderManagersPage( + req, + res, + next, + 'user_membership/publisher-managers-react' + ) +} + async function _renderManagersPage(req, res, next, template) { const { entity, entityConfig } = req return entity.fetchV1Data(function (error, entity) { @@ -178,39 +104,6 @@ async function _renderManagersPage(req, res, next, template) { }) } -function _indexAngular(req, res, next) { - const { entity, entityConfig } = req - return entity.fetchV1Data(function (error, entity) { - if (error != null) { - return next(error) - } - return UserMembershipHandler.getUsers( - entity, - entityConfig, - function (error, users) { - let entityName - if (error != null) { - return next(error) - } - const entityPrimaryKey = - entity[entityConfig.fields.primaryKey].toString() - if (entityConfig.fields.name) { - entityName = entity[entityConfig.fields.name] - } - return res.render('user_membership/index', { - name: entityName, - users, - groupSize: entityConfig.hasMembersLimit - ? entity.membersLimit - : undefined, - translations: entityConfig.translations, - paths: entityConfig.pathsFor(entityPrimaryKey), - }) - } - ) - }) -} - module.exports = { manageGroupMembers, manageGroupManagers, diff --git a/services/web/app/views/subscriptions/canceled-subscription.pug b/services/web/app/views/subscriptions/canceled-subscription.pug deleted file mode 100644 index 5d8c6e4bea..0000000000 --- a/services/web/app/views/subscriptions/canceled-subscription.pug +++ /dev/null @@ -1,15 +0,0 @@ -extends ../layout - -block content - main.content.content-alt#main-content - .container - .row - .col-md-8.col-md-offset-2 - .card(ng-cloak) - .page-header - h2 #{translate("subscription_canceled")} - .alert.alert-info - p #{translate("to_modify_your_subscription_go_to")} - a(href="/user/subscription") #{translate("manage_subscription")}. - p - a.btn.btn-primary(href="/project") < #{translate("back_to_your_projects")} diff --git a/services/web/app/views/subscriptions/dashboard.pug b/services/web/app/views/subscriptions/dashboard.pug deleted file mode 100644 index ff0c069772..0000000000 --- a/services/web/app/views/subscriptions/dashboard.pug +++ /dev/null @@ -1,68 +0,0 @@ -extends ../layout - -include ./dashboard/_team_name_mixin - -block head-scripts - script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js") - -block append meta - meta(name="ol-managedInstitutions", data-type="json", content=managedInstitutions) - meta(name="ol-planCodesChangingAtTermEnd", data-type="json", content=plans.planCodesChangingAtTermEnd) - if (personalSubscription && personalSubscription.recurly) - meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey) - meta(name="ol-subscription" data-type="json" content=personalSubscription) - meta(name="ol-recommendedCurrency" content=personalSubscription.recurly.currency) - meta(name="ol-groupPlans" data-type="json" content=groupPlans) - meta(name="ol-groupPlanModalOptions" data-type="json" content=groupPlanModalOptions) - -block content - main.content.content-alt#main-content(ng-cloak) - .container - .row - .col-md-8.col-md-offset-2 - if (fromPlansPage) - .alert.alert-warning - p You already have a subscription - .card - .page-header - h1 #{translate("your_subscription")} - -var hasDisplayedSubscription = false - if (personalSubscription) - -hasDisplayedSubscription = true - include ./dashboard/_personal_subscription - - if (managedGroupSubscriptions && managedGroupSubscriptions.length > 0) - include ./dashboard/_managed_groups - - if (managedInstitutions && managedInstitutions.length > 0) - include ./dashboard/_managed_institutions - - if (managedPublishers && managedPublishers.length > 0) - include ./dashboard/_managed_publishers - - if (memberGroupSubscriptions && memberGroupSubscriptions.length > 0) - -hasDisplayedSubscription = true - include ./dashboard/_group_memberships - - include ./dashboard/_institution_memberships - - if (v1SubscriptionStatus) - include ./dashboard/_v1_subscription_status - - if (!hasDisplayedSubscription) - if (hasSubscription) - -hasDisplayedSubscription = true - p(ng-non-bindable) You're on an #{settings.appName} Paid plan. Contact - a(href="mailto:support@overleaf.com") support@overleaf.com - | to find out more. - else - p(ng-non-bindable) - | You are on the #{settings.appName} Free plan. Upgrade to access these - a(href="/learn/how-to/Overleaf_premium_features") Premium Features: - ul - li #{translate('invite_more_collabs')} - for feature in ['realtime_track_changes', 'full_doc_history', 'reference_search', 'reference_sync', 'dropbox_integration_lowercase', 'github_integration_lowercase', 'priority_support'] - li #{translate(feature)} - a(ng-controller="UpgradeSubscriptionController" href="/user/subscription/plans" ng-click="upgradeSubscription()").btn.btn-primary Upgrade now - - != moduleIncludes("contactModalGeneral", locals) diff --git a/services/web/app/views/subscriptions/dashboard/_change_plans_mixins.pug b/services/web/app/views/subscriptions/dashboard/_change_plans_mixins.pug deleted file mode 100644 index 76b1ceea8f..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_change_plans_mixins.pug +++ /dev/null @@ -1,28 +0,0 @@ -mixin printPlan(plan) - if (!plan.hideFromUsers) - tr(ng-controller="ChangePlanFormController", ng-init="plan="+JSON.stringify(plan)) - td - strong(ng-non-bindable) #{plan.name} - td - if (plan.annual) - | {{displayPrice}} / #{translate("year")} - else - | {{displayPrice}} / #{translate("month")} - td - if (typeof(personalSubscription.planCode) != "undefined" && plan.planCode == personalSubscription.planCode.split("_")[0]) - if (personalSubscription.pendingPlan) - form - input(type="hidden", ng-model="plan_code", name="plan_code", value=plan.planCode) - input(type="submit", ng-click="cancelPendingPlanChange()", value=translate("keep_current_plan")).btn.btn-primary - else - button.btn.disabled #{translate("your_plan")} - else if (personalSubscription.pendingPlan && typeof(personalSubscription.pendingPlan.planCode) != "undefined" && plan.planCode == personalSubscription.pendingPlan.planCode.split("_")[0]) - button.btn.disabled #{translate("your_new_plan")} - else - form - input(type="hidden", ng-model="plan_code", name="plan_code", value=plan.planCode) - input(type="submit", ng-click="changePlan()", value=translate("change_to_this_plan")).btn.btn-primary - -mixin printPlans(plans) - each plan in plans - +printPlan(plan) diff --git a/services/web/app/views/subscriptions/dashboard/_group_memberships.pug b/services/web/app/views/subscriptions/dashboard/_group_memberships.pug deleted file mode 100644 index 5e17d823c5..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_group_memberships.pug +++ /dev/null @@ -1,32 +0,0 @@ -div(ng-controller="GroupMembershipController") - each groupSubscription, index in memberGroupSubscriptions - unless (groupSubscription.userIsGroupManager) - if (user._id+'' != groupSubscription.admin_id._id+'') - div - p !{translate("you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z", {planName: groupSubscription.planLevelName, groupName: groupSubscription.teamName || '', adminEmail: groupSubscription.admin_id.email}, [{name: 'a', attrs: {href: '/user/subscription/plans'}}, 'strong'])} - if (groupSubscription.teamNotice && groupSubscription.teamNotice != '') - p - //- Team notice is sanitized in SubscriptionViewModelBuilder - em(ng-non-bindable) !{groupSubscription.teamNotice} - if index === memberGroupSubscriptions.length - 1 - include ../_premium_features_link - span - button.btn.btn-danger.text-capitalise(ng-click="removeSelfFromGroup('"+groupSubscription._id+"')") #{translate("leave_group")} - hr - -script(type='text/ng-template', id='LeaveGroupModalTemplate') - .modal-header - h3 #{translate("leave_group")} - .modal-body - p #{translate("sure_you_want_to_leave_group")} - .modal-footer - button.btn.btn-default( - ng-disabled="inflight" - ng-click="cancel()" - ) #{translate("cancel")} - button.btn.btn-danger( - ng-disabled="state.inflight" - ng-click="confirmLeaveGroup()" - ) - span(ng-hide="inflight") #{translate("leave_now")} - span(ng-show="inflight") #{translate("processing")}… diff --git a/services/web/app/views/subscriptions/dashboard/_institution_memberships.pug b/services/web/app/views/subscriptions/dashboard/_institution_memberships.pug deleted file mode 100644 index 50c65a959e..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_institution_memberships.pug +++ /dev/null @@ -1,10 +0,0 @@ -if currentInstitutionsWithLicence === false - .alert.alert-warning - p Sorry, something went wrong. Subscription information related to institutional affiliations may not be displayed. Please try again later. -else - each institution, index in currentInstitutionsWithLicence - -hasDisplayedSubscription = true - p !{translate("you_are_on_x_plan_as_a_confirmed_member_of_institution_y", {planName: 'Professional', institutionName: institution.name || ''}, [{name: 'a', attrs: {href: '/user/subscription/plans'}}, 'strong'])} - if (index === currentInstitutionsWithLicence.length - 1) - include ../_premium_features_link - hr diff --git a/services/web/app/views/subscriptions/dashboard/_managed_groups.pug b/services/web/app/views/subscriptions/dashboard/_managed_groups.pug deleted file mode 100644 index 5bd63f3bf5..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_managed_groups.pug +++ /dev/null @@ -1,24 +0,0 @@ -each managedGroupSubscription in managedGroupSubscriptions - if (managedGroupSubscription.userIsGroupMember) - p !{translate("you_are_a_manager_and_member_of_x_plan_as_member_of_group_subscription_y_administered_by_z", {planName: managedGroupSubscription.planLevelName, groupName: managedGroupSubscription.teamName || '', adminEmail: managedGroupSubscription.admin_id.email}, [{name: 'a', attrs: {href: '/user/subscription/plans'}}, 'strong'])} - else - p !{translate("you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z", {planName: managedGroupSubscription.planLevelName, groupName: managedGroupSubscription.teamName || '', adminEmail: managedGroupSubscription.admin_id.email}, [{name: 'a', attrs: {href: '/user/subscription/plans'}}, 'strong'])} - p - a.btn.btn-primary(href="/manage/groups/" + managedGroupSubscription._id + "/members") - i.fa.fa-fw.fa-users - |   - | Manage members - |   - p - a(href="/manage/groups/" + managedGroupSubscription._id + "/managers") - i.fa.fa-fw.fa-users - |   - | Manage group managers - |   - p - a(href="/metrics/groups/" + managedGroupSubscription._id) - i.fa.fa-fw.fa-line-chart - |   - | View metrics - - hr diff --git a/services/web/app/views/subscriptions/dashboard/_managed_institutions.pug b/services/web/app/views/subscriptions/dashboard/_managed_institutions.pug deleted file mode 100644 index 1844cf68fc..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_managed_institutions.pug +++ /dev/null @@ -1,24 +0,0 @@ -each institution in managedInstitutions - p !{translate("you_are_a_manager_of_commons_at_institution_x", {institutionName: institution.name || ''}, ['strong'])} - p - a.btn.btn-primary(href="/metrics/institutions/" + institution.v1Id) - i.fa.fa-fw.fa-line-chart - |   - | View metrics - p - a(href="/institutions/" + institution.v1Id + "/hub") - i.fa.fa-fw.fa-user-circle - |   - | View hub - p - a(href="/manage/institutions/" + institution.v1Id + "/managers") - i.fa.fa-fw.fa-users - |   - | Manage institution managers - div(ng-controller="MetricsEmailController", ng-cloak) - p - span Monthly metrics emails:  - a(href ng-bind-html="institutionEmailSubscription('"+institution.v1Id+"')" ng-show="!subscriptionChanging" ng-click="changeInstitutionalEmailSubscription('"+institution.v1Id+"')") - span(ng-show="subscriptionChanging") - i.fa.fa-spin.fa-refresh(aria-hidden="true") - hr diff --git a/services/web/app/views/subscriptions/dashboard/_managed_publishers.pug b/services/web/app/views/subscriptions/dashboard/_managed_publishers.pug deleted file mode 100644 index 6a8fcfbc55..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_managed_publishers.pug +++ /dev/null @@ -1,16 +0,0 @@ -each publisher in managedPublishers - p - | You are a manager of - | - strong(ng-non-bindable)= publisher.name - p - a(href="/publishers/" + publisher.slug + "/hub") - i.fa.fa-fw.fa-user-circle - |   - | View hub - p - a(href="/manage/publishers/" + publisher.slug + "/managers") - i.fa.fa-fw.fa-users - |   - | Manage publisher managers - hr diff --git a/services/web/app/views/subscriptions/dashboard/_personal_subscription.pug b/services/web/app/views/subscriptions/dashboard/_personal_subscription.pug deleted file mode 100644 index e0a7501d92..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_personal_subscription.pug +++ /dev/null @@ -1,7 +0,0 @@ -if (personalSubscription.recurly) - include ./_personal_subscription_recurly - include ./_personal_subscription_recurly_sync_email -else - include ./_personal_subscription_custom - -hr diff --git a/services/web/app/views/subscriptions/dashboard/_personal_subscription_custom.pug b/services/web/app/views/subscriptions/dashboard/_personal_subscription_custom.pug deleted file mode 100644 index ec74cf4cd1..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_personal_subscription_custom.pug +++ /dev/null @@ -1,6 +0,0 @@ -p - | Please - | - a(href="/contact") contact support - | - | to make changes to your plan diff --git a/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug b/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug deleted file mode 100644 index edac675cde..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug +++ /dev/null @@ -1,159 +0,0 @@ -div(ng-controller="RecurlySubscriptionController") - div(ng-show="!showCancellation") - if (personalSubscription.recurly.account.has_past_due_invoice && personalSubscription.recurly.account.has_past_due_invoice._ == 'true') - .alert.alert-danger #{translate("account_has_past_due_invoice_change_plan_warning")} - |   - a(href=personalSubscription.recurly.accountManagementLink, target="_blank") #{translate("view_your_invoices")}. - case personalSubscription.recurly.state - when "active" - p !{translate("currently_subscribed_to_plan", {planName: personalSubscription.plan.name}, ['strong'])} - if (personalSubscription.pendingPlan) - if (personalSubscription.pendingPlan.name != personalSubscription.plan.name) - | - | !{translate("your_plan_is_changing_at_term_end", {pendingPlanName: personalSubscription.pendingPlan.name}, ['strong'])} - if (personalSubscription.recurly.pendingAdditionalLicenses > 0 || personalSubscription.recurly.additionalLicenses > 0) - | - | !{translate("pending_additional_licenses", {pendingAdditionalLicenses: personalSubscription.recurly.pendingAdditionalLicenses, pendingTotalLicenses: personalSubscription.recurly.pendingTotalLicenses}, ['strong', 'strong'])} - else if (personalSubscription.recurly.additionalLicenses > 0) - | - | !{translate("additional_licenses", {additionalLicenses: personalSubscription.recurly.additionalLicenses, totalLicenses: personalSubscription.recurly.totalLicenses}, ['strong', 'strong'])} - |   - a(href, ng-click="switchToChangePlanView()", ng-if="showChangePlanButton") !{translate("change_plan")}. - if (personalSubscription.pendingPlan && personalSubscription.pendingPlan.name != personalSubscription.plan.name) - p #{translate("want_change_to_apply_before_plan_end")} - else if (personalSubscription.plan.groupPlan) - p !{translate("contact_support_to_change_group_subscription", {}, [{ name: "a", attrs: { href: "/contact"}}])} - if (personalSubscription.recurly.trialEndsAtFormatted && personalSubscription.recurly.trial_ends_at > Date.now()) - p You're on a free trial which ends on #{personalSubscription.recurly.trialEndsAtFormatted} - p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount: personalSubscription.recurly.displayPrice, collectionDate: personalSubscription.recurly.nextPaymentDueAt}, ['strong', 'strong'])} - include ../_premium_features_link - include ./../_price_exceptions - p.pull-right - p - a(href=personalSubscription.recurly.billingDetailsLink, target="_blank").btn.btn-secondary-info.btn-secondary #{translate("update_your_billing_details")} - |   - a(href=personalSubscription.recurly.accountManagementLink, target="_blank").btn.btn-secondary-info.btn-secondary #{translate("view_your_invoices")} - |   - unless (cancelButtonNewCopy) - a(href, ng-click="switchToCancellationView()", ng-hide="recurlyLoadError", event-tracking='subscription-page-cancel-button-click', event-tracking-mb="true", event-tracking-trigger="click").btn.btn-danger !{translate("stop_your_subscription")} - if (cancelButtonNewCopy) - p - a(href, ng-click="switchToCancellationView()", ng-hide="recurlyLoadError", event-tracking='subscription-page-cancel-button-click', event-tracking-mb="true", event-tracking-trigger="click").btn.btn-danger !{translate("cancel_your_subscription")} - unless (personalSubscription.recurly.trialEndsAtFormatted && personalSubscription.recurly.trial_ends_at > Date.now()) - p - i !{translate("subscription_will_remain_active_until_end_of_billing_period_x", {terminationDate: personalSubscription.recurly.nextPaymentDueAt}, ['strong'])} - when "canceled" - p !{translate("currently_subscribed_to_plan", {planName: personalSubscription.plan.name}, ['strong'])} - p !{translate("subscription_canceled_and_terminate_on_x", {terminateDate: personalSubscription.recurly.nextPaymentDueAt}, ['strong'])} - include ../_premium_features_link - p - a(href=personalSubscription.recurly.accountManagementLink, target="_blank").btn.btn-secondary-info.btn-secondary #{translate("view_your_invoices")} - p: form(action="/user/subscription/reactivate",method="post") - input(type="hidden", name="_csrf", value=csrfToken) - input(type="submit",value="Reactivate your subscription").btn.btn-primary - when "expired" - p !{translate("your_subscription_has_expired")} - p - a(href=personalSubscription.recurly.accountManagementLink, target="_blank").btn.btn-secondary-info.btn-secondary #{translate("view_your_invoices")} - |   - a(href="/user/subscription/plans").btn.btn-primary !{translate("create_new_subscription")} - default - p !{translate("problem_with_subscription_contact_us")} - - .alert.alert-warning(ng-show="recurlyLoadError") - strong #{translate('payment_provider_unreachable_error')} - - include ./_change_plans_mixins - div(ng-show="showChangePlan", ng-cloak) - h2 !{translate("change_plan")} - p: table.table - tr - th !{translate("name")} - th !{translate("price")} - th - +printPlans(plans.studentAccounts) - +printPlans(plans.individualMonthlyPlans) - +printPlans(plans.individualAnnualPlans) - - div(ng-controller="ChangePlanToGroupFormController") - h2 #{translate('looking_multiple_licenses')} - div(ng-show="isValidCurrencyForUpgrade") - span #{translate('reduce_costs_group_licenses')} - br - br - a.btn.btn-primary( - href="#groups" - ng-click="openGroupPlanModal()" - ) #{translate('change_to_group_plan')} - div(ng-hide="isValidCurrencyForUpgrade") - span !{translate('contact_support_to_upgrade_to_group_subscription', {}, [{ name: "a", attrs: { href: "/contact"}}])} - - - .div(ng-controller="RecurlyCancellationController", ng-show="showCancellation").text-center - p - strong #{translate("wed_love_you_to_stay")} - - div(ng-show="showExtendFreeTrial") - p !{translate("have_more_days_to_try", {days:14})} - p - button(type="submit", ng-click="extendTrial()", ng-disabled='inflight').btn.btn-primary #{translate("ill_take_it")} - p - a(href, ng-click="cancelSubscription()", ng-disabled='inflight') #{translate("no_thanks_cancel_now")} - - div(ng-show="showDowngrade") - div(ng-controller="ChangePlanFormController") - p !{translate("interested_in_cheaper_personal_plan", {price:'{{personalDisplayPrice}}'}, ['strong'] )} - p - button(type="submit", ng-click="downgradeToPaidPersonal()", ng-disabled='inflight').btn.btn-primary #{translate("yes_move_me_to_personal_plan")} - p - a(href, ng-click="cancelSubscription()", ng-disabled='inflight') #{translate("no_thanks_cancel_now")} - - div(ng-show="showBasicCancel") - p - a(href, ng-click="switchToDefaultView()").btn.btn-secondary-info.btn-secondary #{translate("i_want_to_stay")} - p - a(href, ng-click="cancelSubscription()", ng-disabled='inflight').btn.btn-primary #{translate("cancel_my_account")} - -script(type='text/ng-template', id='confirmChangePlanModalTemplate') - .modal-header - h3 #{translate("change_plan")} - .modal-body - .alert.alert-warning(ng-show="genericError") - strong #{translate("generic_something_went_wrong")}. #{translate("try_again")}. #{translate("generic_if_problem_continues_contact_us")}. - p !{translate("sure_you_want_to_change_plan", {planName: '{{plan.name}}'}, ['strong'])} - div(ng-show="planChangesAtTermEnd") - p #{translate("existing_plan_active_until_term_end")} - p #{translate("want_change_to_apply_before_plan_end")} - .modal-footer - button.btn.btn-default( - ng-disabled="inflight" - ng-click="cancel()" - ) #{translate("cancel")} - button.btn.btn-primary( - ng-disabled="inflight" - ng-click="confirmChangePlan()" - ) - span(ng-hide="inflight") #{translate("change_plan")} - span(ng-show="inflight") #{translate("processing")}… - -script(type='text/ng-template', id='cancelPendingPlanChangeModalTemplate') - .modal-header - h3 #{translate("change_plan")} - .modal-body - .alert.alert-warning(ng-show="genericError") - strong #{translate("generic_something_went_wrong")}. #{translate("try_again")}. #{translate("generic_if_problem_continues_contact_us")}. - p !{translate("sure_you_want_to_cancel_plan_change", {planName: '{{plan.name}}'}, ['strong'])} - .modal-footer - button.btn.btn-default( - ng-disabled="inflight" - ng-click="cancel()" - ) #{translate("cancel")} - button.btn.btn-primary( - ng-disabled="inflight" - ng-click="confirmCancelPendingPlanChange()" - ) - span(ng-hide="inflight") #{translate("revert_pending_plan_change")} - span(ng-show="inflight") #{translate("processing")}… - -include ../_plans_page_mixins -include ../_modal_group_upgrade diff --git a/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly_sync_email.pug b/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly_sync_email.pug deleted file mode 100644 index 3d58b2ab4a..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly_sync_email.pug +++ /dev/null @@ -1,18 +0,0 @@ --if (user.email !== personalSubscription.recurly.account.email) - div - hr - form(async-form="updateAccountEmailAddress", name="updateAccountEmailAddress", action='/user/subscription/account/email', method="POST") - input(name='_csrf', type='hidden', value=csrfToken) - .form-group - form-messages(for="updateAccountEmailAddress") - .alert.alert-success(ng-show="updateAccountEmailAddress.response.success") - | #{translate('recurly_email_updated')} - div(ng-hide="updateAccountEmailAddress.response.success") - p(ng-non-bindable) !{translate("recurly_email_update_needed", { recurlyEmail: personalSubscription.recurly.account.email, userEmail: user.email }, ['em', 'em'])} - .actions - button.btn-primary.btn( - type='submit', - ng-disabled="updateAccountEmailAddress.inflight" - ) - span(ng-show="!updateAccountEmailAddress.inflight") #{translate("update")} - span(ng-show="updateAccountEmailAddress.inflight") #{translate("updating")}… diff --git a/services/web/app/views/subscriptions/dashboard/_team_name_mixin.pug b/services/web/app/views/subscriptions/dashboard/_team_name_mixin.pug deleted file mode 100644 index f840cecfcb..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_team_name_mixin.pug +++ /dev/null @@ -1,9 +0,0 @@ -mixin teamName(subscription) - if (subscription.teamName && subscription.teamName != '') - strong(ng-non-bindable)= subscription.teamName - else if (subscription.admin_id._id == user._id) - | a group account - else - | the group account owned by - | - strong= subscription.admin_id.email diff --git a/services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug b/services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug deleted file mode 100644 index 54df7bda2f..0000000000 --- a/services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug +++ /dev/null @@ -1,62 +0,0 @@ -if (v1SubscriptionStatus['team'] && v1SubscriptionStatus['team']['default_plan_name'] != 'free') - - hasDisplayedSubscription = true - p - | You have a legacy group licence from Overleaf v1. - if (v1SubscriptionStatus['team']['will_end_at']) - p - | Your current group licence ends on - | - strong= moment(v1SubscriptionStatus['team']['will_end_at']).format('Do MMM YY') - | - | and will - | - if (v1SubscriptionStatus['team']['will_renew']) - | be automatically renewed. - else - | not be automatically renewed. - if (v1SubscriptionStatus['can_cancel_team']) - p - form(method="POST", action="/user/subscription/v1/cancel") - input(type="hidden", name="_csrf", value=csrfToken) - button().btn.btn-danger Stop automatic renewal - else - p - | Please - | - a(href="/contact") contact support - | - | to make changes to your plan - hr - -if (v1SubscriptionStatus['product']) - - hasDisplayedSubscription = true - p - | You have a legacy Overleaf v1 - | - strong= v1SubscriptionStatus['product']['display_name'] - | - | plan. - p - | Your plan ends on - | - strong= moment(v1SubscriptionStatus['product']['will_end_at']).format('Do MMM YY') - | - | and will - | - if (v1SubscriptionStatus['product']['will_renew']) - | be automatically renewed. - else - | not be automatically renewed. - if (v1SubscriptionStatus['can_cancel']) - p - form(method="POST", action="/user/subscription/v1/cancel") - input(type="hidden", name="_csrf", value=csrfToken) - button().btn.btn-danger Stop automatic renewal - else - p - | Please - | - a(href="/contact") contact support - | - | to make changes to your plan - hr diff --git a/services/web/app/views/subscriptions/new-refreshed.pug b/services/web/app/views/subscriptions/new-refreshed.pug deleted file mode 100644 index e1aaa88bde..0000000000 --- a/services/web/app/views/subscriptions/new-refreshed.pug +++ /dev/null @@ -1,364 +0,0 @@ -extends ../layout - -include ./_new_mixins - -block vars - - var suppressNavbarRight = true - - var suppressFooter = true - -block append meta - meta(name="ol-countryCode" content=countryCode) - meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey) - meta(name="ol-recommendedCurrency" content=String(currency).slice(0,3)) - -block head-scripts - script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js") - -block content - main.content.content-alt#main-content - .container(ng-controller="NewSubscriptionController" ng-cloak) - .row.card-group - .col-md-3.col-md-push-1 - .card.card-highlighted - .price-feature-description - h4(ng-if="planName") {{planName}} - h4(ng-if="!planName") #{plan.name} - if plan.features - if plan.features.collaborators === 1 - .text-small.number-of-collaborators #{translate("collabs_per_proj_single", {collabcount: 1})} - if plan.features.collaborators === -1 - .text-small.number-of-collaborators #{translate("unlimited_collabs")} - if plan.features.collaborators > 1 - .text-small.number-of-collaborators #{translate("collabs_per_proj", {collabcount: plan.features.collaborators})} - .text-small #{translate("all_premium_features_including")} - ul.small - if plan.features.compileTimeout > 1 - li #{translate("increased_compile_timeout")} - if plan.features.dropbox && plan.features.github - li #{translate("sync_dropbox_github")} - if plan.features.versioning - li #{translate("full_doc_history")} - if plan.features.trackChanges - li #{translate("track_changes")} - if plan.features.references - li #{translate("reference_search")} - if plan.features.mendeley || plan.features.zotero - li #{translate("reference_sync")} - if plan.features.symbolPalette - li #{translate("symbol_palette")} - - - div.price-summary(ng-if="recurlyPrice") - - var priceVars = { price: "{{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.total }}"}; - hr - h4 #{translate("payment_summary")} - div.small - .price-summary-line - span - | {{planName}} - span(ng-if="coupon") - | {{ availableCurrencies[currencyCode]['symbol'] }}{{ coupon.normalPriceWithoutTax | number:2 }} - span(ng-if="!coupon") - | {{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.subtotal }} - .price-summary-line(ng-if="coupon") - span - | {{ coupon.name }} - span - | –{{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.discount}} - - .price-summary-line(ng-if="taxes && taxes[0] && taxes[0].rate > 0") - span - | #{translate("vat")} {{taxes[0].rate * 100}}% - span - | {{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.tax }} - .price-summary-line.price-summary-total-line - span - b {{ monthlyBilling ? '#{translate("total_per_month")}' : '#{translate("total_per_year")}'}} - span - b {{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.total }} - - - .change-currency - div.dropdown(ng-cloak dropdown) - button.btn.btn-link.dropdown-toggle.change-currency-toggle( - href="#", - data-toggle="dropdown", - dropdown-toggle - ) Change currency - ul.dropdown-menu(role="menu") - li(ng-repeat="(currency, value) in limitedCurrencies") - a( - ng-click="changeCurrency(currency)", - ) - span.change-currency-dropdown-selected-icon(ng-show="currency == currencyCode") - i.fa.fa-check - | {{currency}} ({{value['symbol']}}) - - hr.thin(ng-if="trialLength || coupon") - div.trial-coupon-summary - div(ng-if="trialLength") - - var trialPriceVars = { price: "{{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.total }}", trialLen:'{{trialLength}}' }; - | !{translate("first_x_days_free_after_that_y_per_month", trialPriceVars, ['strong'] )} - - div(ng-if="recurlyPrice") - - var priceVars = { price: "{{ availableCurrencies[currencyCode]['symbol'] }}{{ recurlyPrice.total }}", discountMonths: "{{ coupon.discountMonths }}" }; - span(ng-if="!coupon.singleUse && coupon.discountMonths > 0 && monthlyBilling") - | !{translate("x_price_for_y_months", priceVars, ['strong'] )} - span(ng-if="coupon.singleUse && monthlyBilling") - | !{translate("x_price_for_first_month", priceVars, ['strong'] )} - span(ng-if="coupon.singleUse && !monthlyBilling") - | !{translate("x_price_for_first_year", priceVars, ['strong'] )} - - div(ng-if="coupon && coupon.normalPrice") - - var noDiscountPriceAngularExp = "{{ availableCurrencies[currencyCode]['symbol']}}{{coupon.normalPrice | number:2 }}"; - span(ng-if="!coupon.singleUse && coupon.discountMonths > 0 && monthlyBilling") - | !{translate("then_x_price_per_month", { price: noDiscountPriceAngularExp } )} - span(ng-if="!coupon.singleUse && !coupon.discountMonths && monthlyBilling") - | !{translate("normally_x_price_per_month", { price: noDiscountPriceAngularExp } )} - span(ng-if="!coupon.singleUse && !monthlyBilling") - | !{translate("normally_x_price_per_year", { price: noDiscountPriceAngularExp } )} - span(ng-if="coupon.singleUse && monthlyBilling") - | !{translate("then_x_price_per_month", { price: noDiscountPriceAngularExp } )} - span(ng-if="coupon.singleUse && !monthlyBilling") - | !{translate("then_x_price_per_year", { price: noDiscountPriceAngularExp } )} - hr.thin - - p.price-cancel-anytime.text-center(ng-non-bindable) #{translate("cancel_anytime")} - - .col-md-5.col-md-push-1 - .card.card-highlighted.card-border(ng-hide="threeDSecureFlow") - .alert.alert-danger(ng-show="recurlyLoadError") - strong #{translate('payment_provider_unreachable_error')} - .price-switch-header(ng-hide="recurlyLoadError") - .row - .col-xs-9 - h2 #{translate('select_a_payment_method')} - .row(ng-if="planCode == 'student-annual' || planCode == 'student-monthly' || planCode == 'student_free_trial_7_days'") - .col-xs-12 - p.student-disclaimer #{translate('student_disclaimer')} - - .row(ng-hide="recurlyLoadError") - div() - .col-md-12() - form( - name="simpleCCForm" - novalidate - ) - .alert.alert-warning.small(ng-show="genericError") - strong {{genericError}} - - .alert.alert-warning.small(ng-show="couponError") - strong {{couponError}} - - div - .form-group.payment-method-toggle - hr.thin - .radio - .col-xs-8 - label - input( - type="radio" - ng-model="paymentMethod.value" - name="payment_method" - checked=true - value="credit_card" - ) - strong - | #{translate("card_payment")} - span.hidden-xs - |   - i.fa.fa-cc-visa(aria-hidden="true") - |   - i.fa.fa-cc-mastercard(aria-hidden="true") - |   - i.fa.fa-cc-amex(aria-hidden="true") - - .col-xs-4 - label - input( - type="radio" - ng-model="paymentMethod.value" - name="payment_method" - checked=false - value="paypal" - ) - strong PayPal - span.hidden-xs - |   - i.fa.fa-cc-paypal(aria-hidden="true") - - div(ng-show="paymentMethod.value === 'credit_card'") - .form-group(ng-class="showCardElementInvalid ? 'has-error' : ''") - label(for="recurly-card-input") #{translate("card_details")} - div#recurly-card-input - span.input-feedback-message(ng-if="showCardElementInvalid") Card details are not valid - - .row - .col-xs-6 - .form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-error' : ''") - label(for="first-name") #{translate('first_name')} - input#first-name.form-control( - type="text" - maxlength='255' - data-recurly="first_name" - name="firstName" - ng-model="data.first_name" - required - ) - span.input-feedback-message(ng-if="simpleCCForm.firstName.$error.required") #{translate('this_field_is_required')} - .col-xs-6 - .form-group(ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-error' : ''") - label(for="last-name") #{translate('last_name')} - input#last-name.form-control( - type="text" - maxlength='255' - data-recurly="last_name" - name="lastName" - ng-model="data.last_name" - required - ) - span.input-feedback-message(ng-if="simpleCCForm.lastName.$error.required") #{translate('this_field_is_required')} - - div - .row - .col-xs-12 - .form-group(ng-class="validation.errorFields.address1 || inputHasError(simpleCCForm.address1) ? 'has-error' : ''") - label(for="address-line-1") #{translate('address_line_1')}   - i.fa.fa-question-circle( - aria-label=translate('this_address_will_be_shown_on_the_invoice'), - tooltip=translate('this_address_will_be_shown_on_the_invoice'), - tooltip-placement="right", - tooltip-append-to-body="true", - ) - input#address-line-1.form-control( - type="text" - maxlength="255" - data-recurly="address1" - name="address1" - ng-model="data.address1" - required - ) - span.input-feedback-message(ng-if="simpleCCForm.address1.$error.required") #{translate('this_field_is_required')} - - .row.toggle-address-second-line(ng-hide="ui.showAddressSecondLine") - .col-xs-12 - a.text-small( - href="#" - ng-click="showAddressSecondLine($event)" - ) + Add another address line - - .row(ng-show="ui.showAddressSecondLine") - .col-xs-12 - .form-group.has-feedback(ng-class="validation.errorFields.address2 ? 'has-error' : ''") - label(for="address-line-2") #{translate('address_second_line_optional')} - input#address-line-2.form-control( - type="text" - maxlength="255" - data-recurly="address2" - name="address2" - ng-model="data.address2" - ) - - .row - .col-xs-4 - .form-group(ng-class="validation.errorFields.postal_code || inputHasError(simpleCCForm.postalCode) ? 'has-error' : ''") - label(for="postal-code") #{translate('postal_code')} - input#postal-code.form-control( - type="text" - maxlength="255" - data-recurly="postal_code" - name="postalCode" - ng-model="data.postal_code" - required - ) - span.input-feedback-message(ng-if="simpleCCForm.postalCode.$error.required") #{translate('this_field_is_required')} - - .col-xs-8 - .form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-error' : ''") - label(for="country") #{translate('country')} - select#country.form-control( - data-recurly="country" - ng-model="data.country" - name="country" - ng-change="updateCountry()" - ng-selected="{{country.code == data.country}}" - ng-model-options="{ debounce: 200 }" - required - ) - option(value='', disabled) #{translate("country")} - option(value='-', disabled) -------------- - option(ng-repeat="country in countries" ng-bind-html="country.name" value="{{country.code}}") - span.input-feedback-message(ng-if="simpleCCForm.country.$error.required") #{translate('this_field_is_required')} - - .form-group - .checkbox - label - input( - type="checkbox" - ng-model="ui.addCompanyDetails" - ) - | - | #{translate("add_company_details")} - - .form-group(ng-show="ui.addCompanyDetails") - label(for="company-name") #{translate("company_name")} - input#company-name.form-control( - type="text" - name="companyName" - ng-model="data.company" - ) - - .form-group(ng-show="ui.addCompanyDetails && taxes.length") - label(for="vat-number") #{translate("vat_number")} - input#vat-number.form-control( - type="text" - name="vatNumber" - ng-model="data.vat_number" - ng-blur="applyVatNumber()" - ) - - if (showCouponField) - .form-group - label(for="coupon-code") #{translate('coupon_code')} - input#coupon-code.form-control( - type="text" - ng-blur="applyCoupon()" - ng-model="data.coupon" - ) - - - p(ng-if="paymentMethod.value === 'paypal'") #{translate("proceeding_to_paypal_takes_you_to_the_paypal_site_to_pay")} - - hr.thin - - div.payment-submit - button.btn.btn-primary.btn-block( - ng-click="submit()" - ng-disabled="processing || !isFormValid(simpleCCForm);" - ) - span(ng-show="processing") - i.fa.fa-spinner.fa-spin(aria-hidden="true") - span.sr-only #{translate('processing')} - |   - span(ng-if="paymentMethod.value === 'credit_card'") - | {{ trialLength ? '#{translate("upgrade_cc_btn")}' : '#{translate("upgrade_now")}'}} - span(ng-if="paymentMethod.value !== 'credit_card'") #{translate("proceed_to_paypal")} - - p.tos-agreement-notice !{translate("by_subscribing_you_agree_to_our_terms_of_service", {}, [{name: 'a', attrs: {href: '/legal#Terms', target:'_blank', rel:'noopener noreferrer'}}])} - - div.three-d-secure-container.card.card-highlighted.card-border(ng-show="threeDSecureFlow") - .alert.alert-info.small(aria-live="assertive") - strong #{translate('card_must_be_authenticated_by_3dsecure')} - div.three-d-secure-recurly-container - - script(type="text/javascript", nonce=scriptNonce). - ga('send', 'event', 'pageview', 'payment_form', "#{plan_code}") - - script( - type="text/ng-template" - id="cvv-tooltip-tpl.html" - ) - p !{translate("for_visa_mastercard_and_discover", {}, ['strong', 'strong', 'strong'])} - p !{translate("for_american_express", {}, ['strong', 'strong', 'strong'])} - - +studentCheckModal diff --git a/services/web/app/views/subscriptions/successful-subscription.pug b/services/web/app/views/subscriptions/successful-subscription.pug deleted file mode 100644 index 968ce1734b..0000000000 --- a/services/web/app/views/subscriptions/successful-subscription.pug +++ /dev/null @@ -1,40 +0,0 @@ -extends ../layout - -block content - - var featuresPageVariant= splitTestVariants && splitTestVariants['features-page'] ? splitTestVariants['features-page'] : 'default' - - var featuresLink = featuresPageVariant === 'new' ? "/about/features-overview" : "/learn/how-to/Overleaf_premium_features" - - var featuresTranslationKey = featuresPageVariant === 'new' ? 'get_most_subscription_by_checking_features' : 'get_most_subscription_by_checking_premium_features' - - var featuresLinkSegmentation = {splitTest: 'features-page', splitTestVariant: featuresPageVariant} - - main.content.content-alt#main-content - .container - .row - .col-md-8.col-md-offset-2 - .card(ng-cloak) - .page-header - h2 #{translate("thanks_for_subscribing")} - - .alert.alert-success - if (personalSubscription.recurly.trial_ends_at) - p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount: personalSubscription.recurly.displayPrice, collectionDate: personalSubscription.recurly.nextPaymentDueAt}, ['strong', 'strong'])} - include ./_price_exceptions - p #{translate("to_modify_your_subscription_go_to")} - a(href="/user/subscription") #{translate("manage_subscription")}. - p - if (personalSubscription.groupPlan == true) - a.btn.btn-primary.btn-large(href=`/manage/groups/${personalSubscription._id}/members`) #{translate("add_your_first_group_member_now")} - p.letter-from-founders - p #{translate("thanks_for_subscribing_you_help_sl", {planName:personalSubscription.plan.name})} - p !{translate(featuresTranslationKey, {}, [{name: 'a', attrs: {href: featuresLink, 'event-tracking':'features-page-link', 'event-tracking-trigger': 'click', 'event-tracking-mb': 'true', 'event-segmentation': featuresLinkSegmentation}}])} - p #{translate("need_anything_contact_us_at")} - a(href=`mailto:${settings.adminEmail}`, ng-non-bindable) #{settings.adminEmail} - | . - p !{translate("help_improve_overleaf_fill_out_this_survey", {}, [{name: 'a', attrs: {href: 'https://forms.gle/CdLNX9m6NLxkv1yr5', target:'_blank', rel:'noopener noreferrer'}}])} - p #{translate("regards")}, - br(ng-non-bindable) - | The #{settings.appName} Team - p - if (postCheckoutRedirect) - a.btn.btn-primary(href=postCheckoutRedirect) < #{translate("back_to_your_projects")} - else - a.btn.btn-primary(href="/project") < #{translate("back_to_your_projects")} diff --git a/services/web/app/views/user_membership/index.pug b/services/web/app/views/user_membership/index.pug deleted file mode 100644 index 8e7b6b2cb4..0000000000 --- a/services/web/app/views/user_membership/index.pug +++ /dev/null @@ -1,119 +0,0 @@ -extends ../layout - -block append meta - meta(name="ol-users", data-type="json", content=users) - meta(name="ol-paths", data-type="json", content=paths) - meta(name="ol-groupSize", data-type="json", content=groupSize) - -block content - main.content.content-alt#main-content - .container - .row - .col-md-10.col-md-offset-1 - h1(ng-non-bindable) #{name || translate(translations.title)} - .card(ng-controller="UserMembershipController") - .page-header - .pull-right(ng-cloak) - small(ng-show="groupSize && selectedUsers.length == 0") !{translate("you_have_added_x_of_group_size_y", {addedUsersSize:'{{ users.length }}', groupSize: '{{ groupSize }}'}, ['strong', 'strong'])} - a.btn.btn-danger( - href, - ng-show="selectedUsers.length > 0" - ng-click="removeMembers()" - ) #{translate(translations.remove)} - h3 #{translate(translations.subtitle)} - - .row-spaced-small - div(ng-if="inputs.removeMembers.error", ng-cloak) - div.alert.alert-danger(ng-if="inputs.removeMembers.errorMessage") - | #{translate('error')}: - | {{ inputs.removeMembers.errorMessage }} - div.alert.alert-danger(ng-if="!inputs.removeMembers.errorMessage") - | #{translate('generic_something_went_wrong')} - ul.list-unstyled.structured-list( - select-all-list, - ng-cloak - ) - li.container-fluid - .row - .col-md-4 - input.select-all( - select-all, - type="checkbox" - ) - span.header #{translate("email")} - .col-md-4 - span.header #{translate("name")} - .col-md-2 - span.header( - tooltip=translate('last_active_description') - tooltip-placement="left" - tooltip-append-to-body="true" - ) - | #{translate("last_active")} - sup (?) - .col-md-2 - span.header #{translate("accepted_invite")} - li.container-fluid( - ng-repeat="user in users | orderBy:'email':true", - ng-controller="UserMembershipListItemController" - ) - .row - .col-md-4 - input.select-item( - select-individual, - type="checkbox", - ng-model="user.selected" - ) - span.email {{ user.email }} - .col-md-4 - span.name {{ user.first_name }} {{ user.last_name }} - .col-md-2 - span.lastLogin {{ user.last_active_at | formatDate:'Do MMM YYYY' }} - .col-md-2 - span.registered - i.fa.fa-check.text-success(ng-show="!user.invite" aria-hidden="true") - span.sr-only(ng-show="!user.invite") #{translate('accepted_invite')} - i.fa.fa-times(ng-show="user.invite" aria-hidden="true") - span.sr-only(ng-show="user.invite") #{translate('invite_not_accepted')} - li( - ng-if="users.length == 0", - ng-cloak - ) - .row - .col-md-12.text-centered - small #{translate("no_members")} - - hr - div(ng-if="!groupSize || users.length < groupSize", ng-cloak) - p.small #{translate("add_more_members")} - div(ng-if="inputs.addMembers.error", ng-cloak) - div.alert.alert-danger(ng-if="inputs.addMembers.errorMessage") - | #{translate('error')}: - | {{ inputs.addMembers.errorMessage }} - div.alert.alert-danger(ng-if="!inputs.addMembers.errorMessage") - | #{translate('generic_something_went_wrong')} - form.form - .row - .col-xs-6 - input.form-control( - name="email", - type="text", - placeholder="jane@example.com, joe@example.com", - ng-model="inputs.addMembers.content", - on-enter="addMembers()" - aria-describedby="add-members-description" - ) - .col-xs-4 - button.btn.btn-primary(ng-click="addMembers()", ng-disabled="inputs.addMembers.inflightCount > 0") - span(ng-show="inputs.addMembers.inflightCount === 0") #{translate("add")} - span(ng-show="inputs.addMembers.inflightCount > 0") #{translate("adding")}… - .col-xs-2(ng-if="paths.exportMembers", ng-cloak) - a(href=paths.exportMembers) #{translate('export_csv')} - .row - .col-xs-8 - span.help-block #{translate('add_comma_separated_emails_help')} - - div(ng-if="groupSize && users.length >= groupSize && users.length > 0", ng-cloak) - .row - .col-xs-2.col-xs-offset-10(ng-if="paths.exportMembers", ng-cloak) - a(href=paths.exportMembers) #{translate('export_csv')} diff --git a/services/web/frontend/js/main.js b/services/web/frontend/js/main.js index aaea71f933..0573eece36 100644 --- a/services/web/frontend/js/main.js +++ b/services/web/frontend/js/main.js @@ -14,17 +14,13 @@ import './main/account-settings' import './main/clear-sessions' import './main/account-upgrade-angular' import './main/plans' -import './main/user-membership' import './main/scribtex-popup' import './main/event' import './main/bonus' import './main/system-messages' import './main/translations' -import './main/subscription-dashboard' -import './main/new-subscription' import './main/annual-upgrade' import './main/subscription/team-invite-controller' -import './main/subscription/upgrade-subscription' import './main/learn' import './main/affiliations/components/inputSuggestions' import './main/affiliations/factories/UserAffiliationsDataService' diff --git a/services/web/frontend/js/main/new-subscription.js b/services/web/frontend/js/main/new-subscription.js deleted file mode 100644 index 558f04df06..0000000000 --- a/services/web/frontend/js/main/new-subscription.js +++ /dev/null @@ -1,793 +0,0 @@ -import _ from 'lodash' -/* eslint-disable - camelcase, - max-len, - no-return-assign -*/ -/* global recurly */ -import App from '../base' -import getMeta from '../utils/meta' -import { assign } from '../shared/components/location' - -export default App.controller( - 'NewSubscriptionController', - function ( - $scope, - $modal, - MultiCurrencyPricing, - $http, - $location, - eventTracking - ) { - window.couponCode = $location.search().cc || '' - window.plan_code = $location.search().planCode || '' - window.ITMCampaign = $location.search().itm_campaign || '' - window.ITMContent = $location.search().itm_content || '' - window.ITMReferrer = $location.search().itm_referrer || '' - - if (typeof recurly === 'undefined' || !recurly) { - $scope.recurlyLoadError = true - return - } - - $scope.ui = { - showCurrencyDropdown: false, - showAddressSecondLine: false, - addCompanyDetails: false, - } - - $scope.recurlyLoadError = false - $scope.currencyCode = MultiCurrencyPricing.currencyCode - $scope.initiallySelectedCurrencyCode = MultiCurrencyPricing.currencyCode - $scope.allCurrencies = MultiCurrencyPricing.plans - $scope.availableCurrencies = {} - $scope.planCode = window.plan_code - - const isStudentCheckModalEnabled = - getMeta('ol-splitTestVariants')?.['student-check-modal'] === 'enabled' - - if (isStudentCheckModalEnabled && $scope.planCode.includes('student')) { - $modal.open({ - templateUrl: 'StudentCheckModalTemplate', - controller: 'StudentCheckModalController', - backdrop: 'static', - size: 'dialog-centered', - }) - } - - $scope.switchToStudent = function () { - const currentPlanCode = window.plan_code - const planCode = currentPlanCode.replace('collaborator', 'student') - eventTracking.sendMB('payment-page-switch-to-student', { - plan_code: window.plan_code, - }) - eventTracking.send( - 'subscription-funnel', - 'subscription-form-switch-to-student', - window.plan_code - ) - window.location = - '/user/subscription/new' + - `?planCode=${planCode}` + - `¤cy=${$scope.currencyCode}` + - `&cc=${$scope.data.coupon}` + - `&itm_campaign=${window.ITMCampaign}` + - `&itm_content=${window.ITMContent}` + - `&itm_referrer=${window.ITMReferrer}` - } - - eventTracking.sendMB('payment-page-view', { - plan: window.plan_code, - currency: $scope.currencyCode, - }) - eventTracking.send( - 'subscription-funnel', - 'subscription-form-viewed', - window.plan_code - ) - - $scope.paymentMethod = { value: 'credit_card' } - - $scope.data = { - first_name: '', - last_name: '', - postal_code: '', - address1: '', - address2: '', - state: '', - city: '', - company: '', - vat_number: '', - country: getMeta('ol-countryCode'), - coupon: window.couponCode, - } - - $scope.validation = {} - - $scope.processing = false - - $scope.threeDSecureFlow = false - $scope.threeDSecureContainer = document.querySelector( - '.three-d-secure-container' - ) - $scope.threeDSecureRecurlyContainer = document.querySelector( - '.three-d-secure-recurly-container' - ) - - recurly.configure({ - publicKey: getMeta('ol-recurlyApiKey'), - style: { - all: { - fontFamily: '"Open Sans", sans-serif', - fontSize: '16px', - fontColor: '#7a7a7a', - }, - month: { - placeholder: 'MM', - }, - year: { - placeholder: 'YY', - }, - cvv: { - placeholder: 'CVV', - }, - }, - }) - - const pricing = recurly.Pricing() - window.pricing = pricing - - function setupPricing() { - pricing - .plan(window.plan_code, { quantity: 1 }) - .address({ - country: $scope.data.country, - }) - .tax({ tax_code: 'digital', vat_number: '' }) - .currency($scope.currencyCode) - .coupon($scope.data.coupon) - .catch(function (err) { - if ( - $scope.currencyCode !== 'USD' && - err.name === 'invalid-currency' - ) { - $scope.currencyCode = 'USD' - setupPricing() - } else if (err.name === 'api-error' && err.code === 'not-found') { - // not-found here should refer to the coupon code, plan_code should be valid - $scope.$applyAsync(() => { - $scope.couponError = 'Coupon code is not valid for selected plan' - }) - } else { - // Bail out on other errors, form state will not be correct - $scope.$applyAsync(() => { - $scope.recurlyLoadError = true - }) - throw err - } - }) - .done() - } - - setupPricing() - - pricing.on('change', () => { - $scope.planName = pricing.items.plan.name - - if (pricing.items.plan.trial) { - $scope.trialLength = pricing.items.plan.trial.length - } - - $scope.recurlyPrice = $scope.trialLength - ? pricing.price.next - : pricing.price.now - $scope.taxes = pricing.price.taxes - $scope.monthlyBilling = pricing.items.plan.period.length === 1 - - $scope.availableCurrencies = {} - for (const currencyCode in pricing.items.plan.price) { - if (MultiCurrencyPricing.plans[currencyCode]) { - $scope.availableCurrencies[currencyCode] = - MultiCurrencyPricing.plans[currencyCode] - } - } - - $scope.limitedCurrencies = {} - const limitedCurrencyCodes = ['USD', 'EUR', 'GBP'] - if ( - limitedCurrencyCodes.indexOf($scope.initiallySelectedCurrencyCode) === - -1 - ) { - limitedCurrencyCodes.unshift($scope.initiallySelectedCurrencyCode) - } - limitedCurrencyCodes.forEach(currencyCode => { - $scope.limitedCurrencies[currencyCode] = - MultiCurrencyPricing.plans[currencyCode] - }) - - if ( - pricing.items && - pricing.items.coupon && - pricing.items.coupon.discount && - pricing.items.coupon.discount.type === 'percent' - ) { - const basePrice = parseInt(pricing.price.base.plan.unit, 10) - $scope.coupon = { - singleUse: pricing.items.coupon.single_use, - normalPrice: basePrice, - name: pricing.items.coupon.name, - normalPriceWithoutTax: basePrice, - } - if ( - pricing.items.coupon.applies_for_months > 0 && - pricing.items.coupon.discount.rate && - pricing.items.coupon.applies_for_months - ) { - $scope.coupon.discountMonths = pricing.items.coupon.applies_for_months - $scope.coupon.discountRate = pricing.items.coupon.discount.rate * 100 - } - - if (pricing.price.taxes[0] && pricing.price.taxes[0].rate) { - $scope.coupon.normalPrice += basePrice * pricing.price.taxes[0].rate - } - } else { - $scope.coupon = null - } - $scope.$apply() - }) - - $scope.applyCoupon = () => { - $scope.couponError = '' - pricing - .coupon($scope.data.coupon) - .catch(err => { - if (err.name === 'api-error' && err.code === 'not-found') { - $scope.$applyAsync(() => { - $scope.couponError = 'Coupon code is not valid for selected plan' - }) - } else { - $scope.$applyAsync(() => { - $scope.couponError = - 'An error occured when verifying the coupon code' - }) - throw err - } - }) - .done() - } - - $scope.showAddressSecondLine = function (e) { - e.preventDefault() - $scope.ui.showAddressSecondLine = true - } - - $scope.showCurrencyDropdown = function (e) { - e.preventDefault() - $scope.ui.showCurrencyDropdown = true - } - - const elements = recurly.Elements() - const card = elements.CardElement({ - displayIcon: true, - style: { - inputType: 'mobileSelect', - fontColor: '#5d6879', - placeholder: {}, - invalid: { - fontColor: '#a93529', - }, - }, - }) - card.attach('#recurly-card-input') - card.on('change', state => { - $scope.$applyAsync(() => { - $scope.showCardElementInvalid = - !state.focus && !state.empty && !state.valid - $scope.cardIsValid = state.valid - }) - }) - - $scope.applyVatNumber = () => - pricing - .tax({ tax_code: 'digital', vat_number: $scope.data.vat_number }) - .done() - - $scope.changeCurrency = function (newCurrency) { - $scope.currencyCode = newCurrency - return pricing - .currency(newCurrency) - .catch(function (err) { - if ( - $scope.currencyCode !== 'USD' && - err.name === 'invalid-currency' - ) { - $scope.changeCurrency('USD') - } else { - throw err - } - }) - .done() - } - - $scope.inputHasError = function (formItem) { - if (formItem == null) { - return false - } - - return formItem.$touched && formItem.$invalid - } - - $scope.isFormValid = function (form) { - if ($scope.paymentMethod.value === 'paypal') { - return $scope.data.country !== '' - } else { - return form.$valid && $scope.cardIsValid - } - } - - $scope.updateCountry = () => - pricing.address({ country: $scope.data.country }).done() - - $scope.setPaymentMethod = function (method) { - $scope.paymentMethod.value = method - $scope.validation.errorFields = {} - $scope.genericError = '' - } - - let cachedRecurlyBillingToken - const completeSubscription = function ( - err, - recurlyBillingToken, - recurly3DSecureResultToken - ) { - if (recurlyBillingToken) { - // temporary store the billing token as it might be needed when - // re-sending the request after SCA authentication - cachedRecurlyBillingToken = recurlyBillingToken - } - $scope.validation.errorFields = {} - if (err != null) { - eventTracking.sendMB('payment-page-form-error', err) - eventTracking.send('subscription-funnel', 'subscription-error') - // We may or may not be in a digest loop here depending on - // whether recurly could do validation locally, so do it async - $scope.$evalAsync(function () { - $scope.processing = false - $scope.genericError = err.message - _.each( - err.fields, - field => ($scope.validation.errorFields[field] = true) - ) - }) - } else { - const postData = { - _csrf: window.csrfToken, - recurly_token_id: cachedRecurlyBillingToken.id, - recurly_three_d_secure_action_result_token_id: - recurly3DSecureResultToken && recurly3DSecureResultToken.id, - subscriptionDetails: { - currencyCode: pricing.items.currency, - plan_code: pricing.items.plan.code, - coupon_code: pricing.items.coupon ? pricing.items.coupon.code : '', - first_name: $scope.data.first_name, - last_name: $scope.data.last_name, - isPaypal: $scope.paymentMethod.value === 'paypal', - address: { - address1: $scope.data.address1, - address2: $scope.data.address2, - country: $scope.data.country, - state: $scope.data.state, - zip: $scope.data.postal_code, - }, - ITMCampaign: window.ITMCampaign, - ITMContent: window.ITMContent, - ITMReferrer: window.ITMReferrer, - }, - } - - if ( - postData.subscriptionDetails.isPaypal && - $scope.ui.addCompanyDetails - ) { - postData.subscriptionDetails.billing_info = {} - if ($scope.data.company && $scope.data.company !== '') { - postData.subscriptionDetails.billing_info.company = - $scope.data.company - } - if ($scope.data.vat_number && $scope.data.vat_number !== '') { - postData.subscriptionDetails.billing_info.vat_number = - $scope.data.vat_number - } - } - - eventTracking.sendMB('payment-page-form-submit', { - currencyCode: postData.subscriptionDetails.currencyCode, - plan_code: postData.subscriptionDetails.plan_code, - coupon_code: postData.subscriptionDetails.coupon_code, - isPaypal: postData.subscriptionDetails.isPaypal, - }) - eventTracking.send( - 'subscription-funnel', - 'subscription-form-submitted', - postData.subscriptionDetails.plan_code - ) - return $http - .post('/user/subscription/create', postData) - .then(function () { - eventTracking.sendMB('payment-page-form-success') - eventTracking.send( - 'subscription-funnel', - 'subscription-submission-success', - postData.subscriptionDetails.plan_code - ) - window.location.href = '/user/subscription/thank-you' - }) - .catch(response => { - $scope.processing = false - const { data } = response - $scope.genericError = - (data && data.message) || - 'Something went wrong processing the request' - - if (data.threeDSecureActionTokenId) { - initThreeDSecure(data.threeDSecureActionTokenId) - } - }) - } - } - - $scope.submit = function () { - $scope.processing = true - if ($scope.paymentMethod.value === 'paypal') { - const opts = { description: $scope.planName } - recurly.paypal(opts, completeSubscription) - } else { - const tokenData = _.cloneDeep($scope.data) - if (!$scope.ui.addCompanyDetails) { - delete tokenData.company - delete tokenData.vat_number - } - recurly.token(elements, tokenData, completeSubscription) - } - } - - const initThreeDSecure = function (threeDSecureActionTokenId) { - // instanciate and configure Recurly 3DSecure flow - const risk = recurly.Risk() - const threeDSecure = risk.ThreeDSecure({ - actionTokenId: threeDSecureActionTokenId, - }) - - // on SCA verification error: show payment UI with the error message - threeDSecure.on('error', error => { - $scope.genericError = `Error: ${error.message}` - $scope.threeDSecureFlow = false - $scope.$apply() - }) - - // on SCA verification success: show payment UI in processing mode and - // resubmit the payment with the new token final success or error will be - // handled by `completeSubscription` - threeDSecure.on('token', recurly3DSecureResultToken => { - completeSubscription(null, null, recurly3DSecureResultToken) - $scope.genericError = null - $scope.threeDSecureFlow = false - $scope.processing = true - $scope.$apply() - }) - - // make sure the threeDSecureRecurlyContainer is empty (in case of - // retries) and show 3DSecure UI - $scope.threeDSecureRecurlyContainer.innerHTML = '' - $scope.threeDSecureFlow = true - threeDSecure.attach($scope.threeDSecureRecurlyContainer) - - // scroll the UI into view (timeout needed to make sure the element is - // visible) - window.setTimeout(() => { - $scope.threeDSecureContainer.scrollIntoView() - }, 0) - } - - // list taken from Recurly (see https://docs.recurly.com/docs/countries-provinces-and-states). Country code must exist on Recurly, so update with care - $scope.countries = [ - { code: 'AF', name: 'Afghanistan' }, - { code: 'AX', name: 'Åland Islands' }, - { code: 'AL', name: 'Albania' }, - { code: 'DZ', name: 'Algeria' }, - { code: 'AS', name: 'American Samoa' }, - { code: 'AD', name: 'Andorra' }, - { code: 'AO', name: 'Angola' }, - { code: 'AI', name: 'Anguilla' }, - { code: 'AQ', name: 'Antarctica' }, - { code: 'AG', name: 'Antigua and Barbuda' }, - { code: 'AR', name: 'Argentina' }, - { code: 'AM', name: 'Armenia' }, - { code: 'AW', name: 'Aruba' }, - { code: 'AC', name: 'Ascension Island' }, - { code: 'AU', name: 'Australia' }, - { code: 'AT', name: 'Austria' }, - { code: 'AZ', name: 'Azerbaijan' }, - { code: 'BS', name: 'Bahamas' }, - { code: 'BH', name: 'Bahrain' }, - { code: 'BD', name: 'Bangladesh' }, - { code: 'BB', name: 'Barbados' }, - { code: 'BY', name: 'Belarus' }, - { code: 'BE', name: 'Belgium' }, - { code: 'BZ', name: 'Belize' }, - { code: 'BJ', name: 'Benin' }, - { code: 'BM', name: 'Bermuda' }, - { code: 'BT', name: 'Bhutan' }, - { code: 'BO', name: 'Bolivia' }, - { code: 'BA', name: 'Bosnia and Herzegovina' }, - { code: 'BW', name: 'Botswana' }, - { code: 'BV', name: 'Bouvet Island' }, - { code: 'BR', name: 'Brazil' }, - { code: 'BQ', name: 'British Antarctic Territory' }, - { code: 'IO', name: 'British Indian Ocean Territory' }, - { code: 'VG', name: 'British Virgin Islands' }, - { code: 'BN', name: 'Brunei' }, - { code: 'BG', name: 'Bulgaria' }, - { code: 'BF', name: 'Burkina Faso' }, - { code: 'BI', name: 'Burundi' }, - { code: 'CV', name: 'Cabo Verde' }, - { code: 'KH', name: 'Cambodia' }, - { code: 'CM', name: 'Cameroon' }, - { code: 'CA', name: 'Canada' }, - { code: 'IC', name: 'Canary Islands' }, - { code: 'CT', name: 'Canton and Enderbury Islands' }, - { code: 'KY', name: 'Cayman Islands' }, - { code: 'CF', name: 'Central African Republic' }, - { code: 'EA', name: 'Ceuta and Melilla' }, - { code: 'TD', name: 'Chad' }, - { code: 'CL', name: 'Chile' }, - { code: 'CN', name: 'China' }, - { code: 'CX', name: 'Christmas Island' }, - { code: 'CP', name: 'Clipperton Island' }, - { code: 'CC', name: 'Cocos [Keeling] Islands' }, - { code: 'CO', name: 'Colombia' }, - { code: 'KM', name: 'Comoros' }, - { code: 'CG', name: 'Congo - Brazzaville' }, - { code: 'CD', name: 'Congo - Kinshasa' }, - { code: 'CD', name: 'Congo [DRC]' }, - { code: 'CG', name: 'Congo [Republic]' }, - { code: 'CK', name: 'Cook Islands' }, - { code: 'CR', name: 'Costa Rica' }, - { code: 'CI', name: 'Côte d’Ivoire' }, - { code: 'HR', name: 'Croatia' }, - { code: 'CU', name: 'Cuba' }, - { code: 'CY', name: 'Cyprus' }, - { code: 'CZ', name: 'Czech Republic' }, - { code: 'DK', name: 'Denmark' }, - { code: 'DG', name: 'Diego Garcia' }, - { code: 'DJ', name: 'Djibouti' }, - { code: 'DM', name: 'Dominica' }, - { code: 'DO', name: 'Dominican Republic' }, - { code: 'NQ', name: 'Dronning Maud Land' }, - { code: 'TL', name: 'East Timor' }, - { code: 'EC', name: 'Ecuador' }, - { code: 'EG', name: 'Egypt' }, - { code: 'SV', name: 'El Salvador' }, - { code: 'GQ', name: 'Equatorial Guinea' }, - { code: 'ER', name: 'Eritrea' }, - { code: 'EE', name: 'Estonia' }, - { code: 'ET', name: 'Ethiopia' }, - { code: 'FK', name: 'Falkland Islands [Islas Malvinas]' }, - { code: 'FK', name: 'Falkland Islands' }, - { code: 'FO', name: 'Faroe Islands' }, - { code: 'FJ', name: 'Fiji' }, - { code: 'FI', name: 'Finland' }, - { code: 'FR', name: 'France' }, - { code: 'GF', name: 'French Guiana' }, - { code: 'PF', name: 'French Polynesia' }, - { code: 'FQ', name: 'French Southern and Antarctic Territories' }, - { code: 'TF', name: 'French Southern Territories' }, - { code: 'GA', name: 'Gabon' }, - { code: 'GM', name: 'Gambia' }, - { code: 'GE', name: 'Georgia' }, - { code: 'DE', name: 'Germany' }, - { code: 'GH', name: 'Ghana' }, - { code: 'GI', name: 'Gibraltar' }, - { code: 'GR', name: 'Greece' }, - { code: 'GL', name: 'Greenland' }, - { code: 'GD', name: 'Grenada' }, - { code: 'GP', name: 'Guadeloupe' }, - { code: 'GU', name: 'Guam' }, - { code: 'GT', name: 'Guatemala' }, - { code: 'GG', name: 'Guernsey' }, - { code: 'GW', name: 'Guinea-Bissau' }, - { code: 'GN', name: 'Guinea' }, - { code: 'GY', name: 'Guyana' }, - { code: 'HT', name: 'Haiti' }, - { code: 'HM', name: 'Heard Island and McDonald Islands' }, - { code: 'HN', name: 'Honduras' }, - { code: 'HK', name: 'Hong Kong' }, - { code: 'HU', name: 'Hungary' }, - { code: 'IS', name: 'Iceland' }, - { code: 'IN', name: 'India' }, - { code: 'ID', name: 'Indonesia' }, - { code: 'IR', name: 'Iran' }, - { code: 'IQ', name: 'Iraq' }, - { code: 'IE', name: 'Ireland' }, - { code: 'IM', name: 'Isle of Man' }, - { code: 'IL', name: 'Israel' }, - { code: 'IT', name: 'Italy' }, - { code: 'CI', name: 'Ivory Coast' }, - { code: 'JM', name: 'Jamaica' }, - { code: 'JP', name: 'Japan' }, - { code: 'JE', name: 'Jersey' }, - { code: 'JT', name: 'Johnston Island' }, - { code: 'JO', name: 'Jordan' }, - { code: 'KZ', name: 'Kazakhstan' }, - { code: 'KE', name: 'Kenya' }, - { code: 'KI', name: 'Kiribati' }, - { code: 'KW', name: 'Kuwait' }, - { code: 'KG', name: 'Kyrgyzstan' }, - { code: 'LA', name: 'Laos' }, - { code: 'LV', name: 'Latvia' }, - { code: 'LB', name: 'Lebanon' }, - { code: 'LS', name: 'Lesotho' }, - { code: 'LR', name: 'Liberia' }, - { code: 'LY', name: 'Libya' }, - { code: 'LI', name: 'Liechtenstein' }, - { code: 'LT', name: 'Lithuania' }, - { code: 'LU', name: 'Luxembourg' }, - { code: 'MO', name: 'Macau SAR China' }, - { code: 'MO', name: 'Macau' }, - { code: 'MK', name: 'Macedonia [FYROM]' }, - { code: 'MK', name: 'Macedonia' }, - { code: 'MG', name: 'Madagascar' }, - { code: 'MW', name: 'Malawi' }, - { code: 'MY', name: 'Malaysia' }, - { code: 'MV', name: 'Maldives' }, - { code: 'ML', name: 'Mali' }, - { code: 'MT', name: 'Malta' }, - { code: 'MH', name: 'Marshall Islands' }, - { code: 'MQ', name: 'Martinique' }, - { code: 'MR', name: 'Mauritania' }, - { code: 'MU', name: 'Mauritius' }, - { code: 'YT', name: 'Mayotte' }, - { code: 'FX', name: 'Metropolitan France' }, - { code: 'MX', name: 'Mexico' }, - { code: 'FM', name: 'Micronesia' }, - { code: 'MI', name: 'Midway Islands' }, - { code: 'MD', name: 'Moldova' }, - { code: 'MC', name: 'Monaco' }, - { code: 'MN', name: 'Mongolia' }, - { code: 'ME', name: 'Montenegro' }, - { code: 'MS', name: 'Montserrat' }, - { code: 'MA', name: 'Morocco' }, - { code: 'MZ', name: 'Mozambique' }, - { code: 'MM', name: 'Myanmar [Burma]' }, - { code: 'NA', name: 'Namibia' }, - { code: 'NR', name: 'Nauru' }, - { code: 'NP', name: 'Nepal' }, - { code: 'AN', name: 'Netherlands Antilles' }, - { code: 'NL', name: 'Netherlands' }, - { code: 'NC', name: 'New Caledonia' }, - { code: 'NZ', name: 'New Zealand' }, - { code: 'NI', name: 'Nicaragua' }, - { code: 'NE', name: 'Niger' }, - { code: 'NG', name: 'Nigeria' }, - { code: 'NU', name: 'Niue' }, - { code: 'NF', name: 'Norfolk Island' }, - { code: 'KP', name: 'North Korea' }, - { code: 'VD', name: 'North Vietnam' }, - { code: 'MP', name: 'Northern Mariana Islands' }, - { code: 'NO', name: 'Norway' }, - { code: 'OM', name: 'Oman' }, - { code: 'QO', name: 'Outlying Oceania' }, - { code: 'PC', name: 'Pacific Islands Trust Territory' }, - { code: 'PK', name: 'Pakistan' }, - { code: 'PW', name: 'Palau' }, - { code: 'PS', name: 'Palestinian Territories' }, - { code: 'PZ', name: 'Panama Canal Zone' }, - { code: 'PA', name: 'Panama' }, - { code: 'PG', name: 'Papua New Guinea' }, - { code: 'PY', name: 'Paraguay' }, - { code: 'YD', name: "People's Democratic Republic of Yemen" }, - { code: 'PE', name: 'Peru' }, - { code: 'PH', name: 'Philippines' }, - { code: 'PN', name: 'Pitcairn Islands' }, - { code: 'PL', name: 'Poland' }, - { code: 'PT', name: 'Portugal' }, - { code: 'PR', name: 'Puerto Rico' }, - { code: 'QA', name: 'Qatar' }, - { code: 'RE', name: 'Réunion' }, - { code: 'RO', name: 'Romania' }, - { code: 'RU', name: 'Russia' }, - { code: 'RW', name: 'Rwanda' }, - { code: 'BL', name: 'Saint Barthélemy' }, - { code: 'SH', name: 'Saint Helena' }, - { code: 'KN', name: 'Saint Kitts and Nevis' }, - { code: 'LC', name: 'Saint Lucia' }, - { code: 'MF', name: 'Saint Martin' }, - { code: 'PM', name: 'Saint Pierre and Miquelon' }, - { code: 'VC', name: 'Saint Vincent and the Grenadines' }, - { code: 'WS', name: 'Samoa' }, - { code: 'SM', name: 'San Marino' }, - { code: 'ST', name: 'São Tomé and Príncipe' }, - { code: 'SA', name: 'Saudi Arabia' }, - { code: 'SN', name: 'Senegal' }, - { code: 'CS', name: 'Serbia and Montenegro' }, - { code: 'RS', name: 'Serbia' }, - { code: 'SC', name: 'Seychelles' }, - { code: 'SL', name: 'Sierra Leone' }, - { code: 'SG', name: 'Singapore' }, - { code: 'SK', name: 'Slovakia' }, - { code: 'SI', name: 'Slovenia' }, - { code: 'SB', name: 'Solomon Islands' }, - { code: 'SO', name: 'Somalia' }, - { code: 'ZA', name: 'South Africa' }, - { code: 'GS', name: 'South Georgia and the South Sandwich Islands' }, - { code: 'KR', name: 'South Korea' }, - { code: 'ES', name: 'Spain' }, - { code: 'LK', name: 'Sri Lanka' }, - { code: 'SD', name: 'Sudan' }, - { code: 'SR', name: 'Suriname' }, - { code: 'SJ', name: 'Svalbard and Jan Mayen' }, - { code: 'SZ', name: 'Swaziland' }, - { code: 'SE', name: 'Sweden' }, - { code: 'CH', name: 'Switzerland' }, - { code: 'SY', name: 'Syria' }, - { code: 'TW', name: 'Taiwan' }, - { code: 'TJ', name: 'Tajikistan' }, - { code: 'TZ', name: 'Tanzania' }, - { code: 'TH', name: 'Thailand' }, - { code: 'TL', name: 'Timor-Leste' }, - { code: 'TG', name: 'Togo' }, - { code: 'TK', name: 'Tokelau' }, - { code: 'TO', name: 'Tonga' }, - { code: 'TT', name: 'Trinidad and Tobago' }, - { code: 'TA', name: 'Tristan da Cunha' }, - { code: 'TN', name: 'Tunisia' }, - { code: 'TR', name: 'Turkey' }, - { code: 'TM', name: 'Turkmenistan' }, - { code: 'TC', name: 'Turks and Caicos Islands' }, - { code: 'TV', name: 'Tuvalu' }, - { code: 'UM', name: 'U.S. Minor Outlying Islands' }, - { code: 'PU', name: 'U.S. Miscellaneous Pacific Islands' }, - { code: 'VI', name: 'U.S. Virgin Islands' }, - { code: 'UG', name: 'Uganda' }, - { code: 'UA', name: 'Ukraine' }, - { code: 'AE', name: 'United Arab Emirates' }, - { code: 'GB', name: 'United Kingdom' }, - { code: 'US', name: 'United States' }, - { code: 'UY', name: 'Uruguay' }, - { code: 'UZ', name: 'Uzbekistan' }, - { code: 'VU', name: 'Vanuatu' }, - { code: 'VA', name: 'Vatican City' }, - { code: 'VE', name: 'Venezuela' }, - { code: 'VN', name: 'Vietnam' }, - { code: 'WK', name: 'Wake Island' }, - { code: 'WF', name: 'Wallis and Futuna' }, - { code: 'EH', name: 'Western Sahara' }, - { code: 'YE', name: 'Yemen' }, - { code: 'ZM', name: 'Zambia' }, - { code: 'ZW', name: 'Zimbabwe' }, - ] - } -) - -App.controller( - 'StudentCheckModalController', - function ($scope, $modalInstance, eventTracking) { - $modalInstance.rendered.then(() => { - eventTracking.sendMB('student-check-displayed') - }) - - $scope.browsePlans = () => { - if (document.referrer?.includes('/user/subscription/choose-your-plan')) { - // redirect to interstitial page with `itm_referrer` param - assign( - '/user/subscription/choose-your-plan?itm_referrer=student-status-declined' - ) - } else { - // redirect to plans page with `itm_referrer` param - assign('/user/subscription/plans?itm_referrer=student-status-declined') - } - } - - $scope.confirm = () => $modalInstance.dismiss('cancel') - } -) diff --git a/services/web/frontend/js/main/subscription-dashboard.js b/services/web/frontend/js/main/subscription-dashboard.js deleted file mode 100644 index f3f7272c32..0000000000 --- a/services/web/frontend/js/main/subscription-dashboard.js +++ /dev/null @@ -1,451 +0,0 @@ -import _ from 'lodash' -/* global recurly */ - -/* eslint-disable - camelcase, - max-len, - no-return-assign, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS104: Avoid inline assignments - * DS204: Change includes calls to have a more natural evaluation order - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -import App from '../base' -import getMeta from '../utils/meta' -const SUBSCRIPTION_URL = '/user/subscription/update' - -const GROUP_PLAN_MODAL_OPTIONS = getMeta('ol-groupPlanModalOptions') - -const ensureRecurlyIsSetup = _.once(() => { - if (typeof recurly === 'undefined' || !recurly) { - return false - } - recurly.configure(getMeta('ol-recurlyApiKey')) - return true -}) - -function getPricePerUser(price, currencySymbol, size) { - let perUserPrice = price / size - if (perUserPrice % 1 !== 0) { - perUserPrice = perUserPrice.toFixed(2) - } - return `${currencySymbol}${perUserPrice}` -} - -App.controller('MetricsEmailController', function ($scope, $http) { - $scope.institutionEmailSubscription = function (institutionId) { - const inst = _.find(window.managedInstitutions, function (institution) { - return institution.v1Id === parseInt(institutionId) - }) - if (inst.metricsEmail.optedOutUserIds.includes(window.user_id)) { - return 'Subscribe' - } else { - return 'Unsubscribe' - } - } - - $scope.changeInstitutionalEmailSubscription = function (institutionId) { - $scope.subscriptionChanging = true - return $http({ - method: 'POST', - url: `/institutions/${institutionId}/emailSubscription`, - headers: { - 'X-CSRF-Token': window.csrfToken, - }, - }).then(function successCallback(response) { - window.managedInstitutions = _.map( - window.managedInstitutions, - function (institution) { - if (institution.v1Id === parseInt(institutionId)) { - institution.metricsEmail.optedOutUserIds = response.data - } - return institution - } - ) - $scope.subscriptionChanging = false - }) - } -}) - -App.factory('RecurlyPricing', function ($q, MultiCurrencyPricing) { - return { - loadDisplayPriceWithTax: function (planCode, currency, taxRate) { - if (!ensureRecurlyIsSetup()) return - const currencySymbol = MultiCurrencyPricing.plans[currency].symbol - const pricing = recurly.Pricing() - return $q(function (resolve, reject) { - pricing - .plan(planCode, { quantity: 1 }) - .currency(currency) - .done(function (price) { - const totalPriceExTax = parseFloat(price.next.total) - let taxAmount = totalPriceExTax * taxRate - if (isNaN(taxAmount)) { - taxAmount = 0 - } - let total = totalPriceExTax + taxAmount - if (total % 1 !== 0) { - total = total.toFixed(2) - } - resolve({ - total: `${currencySymbol}${total}`, - totalValue: total, - subtotal: `${currencySymbol}${totalPriceExTax.toFixed(2)}`, - tax: `${currencySymbol}${taxAmount.toFixed(2)}`, - includesTax: taxAmount !== 0, - }) - }) - }) - }, - } -}) - -App.controller('ChangePlanToGroupFormController', function ($scope, $modal) { - if (!ensureRecurlyIsSetup()) return - - const subscription = getMeta('ol-subscription') - const currency = subscription.recurly.currency - - const validCurrencies = GROUP_PLAN_MODAL_OPTIONS.currencies.map( - item => item.code - ) - - if (validCurrencies.includes(currency)) { - $scope.isValidCurrencyForUpgrade = true - } - - $scope.openGroupPlanModal = function () { - const planCode = subscription.plan.planCode - $scope.defaultGroupPlan = planCode.includes('professional') - ? 'professional' - : 'collaborator' - $scope.currentPlanCurrency = currency - $modal.open({ - templateUrl: 'groupPlanModalUpgradeTemplate', - controller: 'GroupPlansModalUpgradeController', - scope: $scope, - }) - } -}) - -App.controller( - 'GroupPlansModalUpgradeController', - function ($scope, $modal, $location, $http, RecurlyPricing) { - $scope.options = GROUP_PLAN_MODAL_OPTIONS - - $scope.groupPlans = getMeta('ol-groupPlans') - - const currency = $scope.currentPlanCurrency - - // default selected - $scope.selected = { - plan_code: $scope.defaultGroupPlan || 'collaborator', - currency, - size: '10', - usage: 'enterprise', - } - - $scope.recalculatePrice = function () { - const subscription = getMeta('ol-subscription') - const { taxRate } = subscription.recurly - const { usage, plan_code, currency, size } = $scope.selected - $scope.discountEligible = size >= 10 - const recurlyPricePlaceholder = { total: '...' } - let perUserDisplayPricePlaceholder = '...' - const currencySymbol = $scope.options.currencySymbols[currency] - if (taxRate === 0) { - const basePriceInCents = - $scope.groupPlans[usage][plan_code][currency][size].price_in_cents - const basePriceInUnit = (basePriceInCents / 100).toFixed() - recurlyPricePlaceholder.total = `${currencySymbol}${basePriceInUnit}` - perUserDisplayPricePlaceholder = getPricePerUser( - basePriceInUnit, - currencySymbol, - size - ) - } - $scope.recurlyPrice = recurlyPricePlaceholder // Placeholder while we talk to recurly - $scope.perUserDisplayPrice = perUserDisplayPricePlaceholder // Placeholder while we talk to recurly - const recurlyPlanCode = `group_${plan_code}_${size}_${usage}` - RecurlyPricing.loadDisplayPriceWithTax( - recurlyPlanCode, - currency, - taxRate - ).then(price => { - $scope.recurlyPrice = price - $scope.perUserDisplayPrice = getPricePerUser( - price.totalValue, - currencySymbol, - size - ) - }) - } - - $scope.$watch('selected', $scope.recalculatePrice, true) - $scope.recalculatePrice() - - $scope.upgrade = function () { - const { plan_code, size, usage } = $scope.selected - const body = { - _csrf: window.csrfToken, - plan_code: `group_${plan_code}_${size}_${usage}`, - } - $scope.inflight = true - $http - .post(`/user/subscription/update`, body) - .then(() => location.reload()) - } - } -) - -App.controller( - 'ChangePlanFormController', - function ($scope, $modal, RecurlyPricing) { - if (!ensureRecurlyIsSetup()) return - - function stripCentsIfZero(displayPrice) { - return displayPrice ? displayPrice.replace(/\.00$/, '') : '...' - } - - $scope.changePlan = () => - $modal.open({ - templateUrl: 'confirmChangePlanModalTemplate', - controller: 'ConfirmChangePlanController', - scope: $scope, - }) - - $scope.cancelPendingPlanChange = () => - $modal.open({ - templateUrl: 'cancelPendingPlanChangeModalTemplate', - controller: 'CancelPendingPlanChangeController', - scope: $scope, - }) - - $scope.$watch('plan', function (plan) { - if (!plan) return - const planCodesChangingAtTermEnd = getMeta( - 'ol-planCodesChangingAtTermEnd' - ) - $scope.planChangesAtTermEnd = false - if ( - planCodesChangingAtTermEnd && - planCodesChangingAtTermEnd.indexOf(plan.planCode) > -1 - ) { - $scope.planChangesAtTermEnd = true - } - const planCode = plan.planCode - const subscription = getMeta('ol-subscription') - const { currency, taxRate } = subscription.recurly - if (subscription.recurly.displayPrice) { - if (subscription.pendingPlan?.planCode === planCode) { - $scope.displayPrice = stripCentsIfZero( - subscription.recurly.displayPrice - ) - return - } - if (subscription.planCode === planCode) { - if (subscription.pendingPlan) { - $scope.displayPrice = stripCentsIfZero( - subscription.recurly.currentPlanDisplayPrice - ) - } else { - $scope.displayPrice = stripCentsIfZero( - subscription.recurly.displayPrice - ) - } - return - } - } - $scope.displayPrice = '...' // Placeholder while we talk to recurly - RecurlyPricing.loadDisplayPriceWithTax(planCode, currency, taxRate).then( - recurlyPrice => { - $scope.displayPrice = recurlyPrice.total - } - ) - }) - } -) - -App.controller( - 'ConfirmChangePlanController', - function ($scope, $modalInstance, $http) { - $scope.confirmChangePlan = function () { - const body = { - plan_code: $scope.plan.planCode, - _csrf: window.csrfToken, - } - - $scope.genericError = false - $scope.inflight = true - - return $http - .post(`${SUBSCRIPTION_URL}?origin=confirmChangePlan`, body) - .then(() => location.reload()) - .catch(() => { - $scope.genericError = true - $scope.inflight = false - }) - } - - return ($scope.cancel = () => $modalInstance.dismiss('cancel')) - } -) - -App.controller( - 'CancelPendingPlanChangeController', - function ($scope, $modalInstance, $http) { - $scope.confirmCancelPendingPlanChange = function () { - const body = { - _csrf: window.csrfToken, - } - - $scope.genericError = false - $scope.inflight = true - - return $http - .post('/user/subscription/cancel-pending', body) - .then(() => location.reload()) - .catch(() => { - $scope.genericError = true - $scope.inflight = false - }) - } - - return ($scope.cancel = () => $modalInstance.dismiss('cancel')) - } -) - -App.controller( - 'LeaveGroupModalController', - function ($scope, $modalInstance, $http) { - $scope.confirmLeaveGroup = function () { - $scope.inflight = true - return $http({ - url: '/subscription/group/user', - method: 'DELETE', - params: { - subscriptionId: $scope.subscriptionId, - _csrf: window.csrfToken, - }, - }) - .then(() => window.location.reload()) - .catch(() => console.log('something went wrong changing plan')) - } - - return ($scope.cancel = () => $modalInstance.dismiss('cancel')) - } -) - -App.controller('GroupMembershipController', function ($scope, $modal) { - $scope.removeSelfFromGroup = function (subscriptionId) { - $scope.subscriptionId = subscriptionId - return $modal.open({ - templateUrl: 'LeaveGroupModalTemplate', - controller: 'LeaveGroupModalController', - scope: $scope, - }) - } -}) - -App.controller('RecurlySubscriptionController', function ($scope) { - const recurlyIsSetup = ensureRecurlyIsSetup() - const subscription = getMeta('ol-subscription') - $scope.showChangePlanButton = recurlyIsSetup && !subscription.groupPlan - if ( - window.subscription.recurly.account.has_past_due_invoice && - window.subscription.recurly.account.has_past_due_invoice._ === 'true' - ) { - $scope.showChangePlanButton = false - } - $scope.recurlyLoadError = !recurlyIsSetup - - $scope.switchToDefaultView = () => { - $scope.showCancellation = false - $scope.showChangePlan = false - } - $scope.switchToDefaultView() - - $scope.switchToCancellationView = () => { - $scope.showCancellation = true - $scope.showChangePlan = false - } - - $scope.switchToChangePlanView = () => { - $scope.showCancellation = false - $scope.showChangePlan = true - } -}) - -App.controller( - 'RecurlyCancellationController', - function ($scope, RecurlyPricing, $http) { - if (!ensureRecurlyIsSetup()) return - const subscription = getMeta('ol-subscription') - const sevenDaysTime = new Date() - sevenDaysTime.setDate(sevenDaysTime.getDate() + 7) - const freeTrialEndDate = new Date(subscription.recurly.trial_ends_at) - const freeTrialInFuture = freeTrialEndDate > new Date() - const freeTrialExpiresUnderSevenDays = freeTrialEndDate < sevenDaysTime - - const isMonthlyCollab = - subscription.plan.planCode.indexOf('collaborator') !== -1 && - subscription.plan.planCode.indexOf('ann') === -1 && - !subscription.groupPlan - const stillInFreeTrial = freeTrialInFuture && freeTrialExpiresUnderSevenDays - - if (isMonthlyCollab && stillInFreeTrial) { - $scope.showExtendFreeTrial = true - } else if (isMonthlyCollab && !stillInFreeTrial) { - $scope.showDowngrade = true - } else { - $scope.showBasicCancel = true - } - - const planCode = 'paid-personal' - const { currency, taxRate } = subscription.recurly - $scope.personalDisplayPrice = '...' // Placeholder while we talk to recurly - RecurlyPricing.loadDisplayPriceWithTax(planCode, currency, taxRate).then( - price => { - $scope.personalDisplayPrice = price.total - } - ) - - $scope.downgradeToPaidPersonal = function () { - const body = { - plan_code: planCode, - _csrf: window.csrfToken, - } - $scope.inflight = true - return $http - .post(`${SUBSCRIPTION_URL}?origin=downgradeToPaidPersonal`, body) - .then(() => location.reload()) - .catch(() => console.log('something went wrong changing plan')) - } - - $scope.cancelSubscription = function () { - const body = { _csrf: window.csrfToken } - - $scope.inflight = true - return $http - .post('/user/subscription/cancel', body) - .then(() => (location.href = '/user/subscription/canceled')) - .catch(() => console.log('something went wrong changing plan')) - } - - $scope.extendTrial = function () { - const body = { _csrf: window.csrfToken } - $scope.inflight = true - return $http - .put('/user/subscription/extend', body) - .then(() => location.reload()) - .catch(() => console.log('something went wrong changing plan')) - } - } -) diff --git a/services/web/frontend/js/main/subscription/upgrade-subscription.js b/services/web/frontend/js/main/subscription/upgrade-subscription.js deleted file mode 100644 index 20dccdef31..0000000000 --- a/services/web/frontend/js/main/subscription/upgrade-subscription.js +++ /dev/null @@ -1,13 +0,0 @@ -import App from '../../base' - -export default App.controller( - 'UpgradeSubscriptionController', - function ($scope, eventTracking) { - $scope.upgradeSubscription = function () { - eventTracking.send('subscription-funnel', 'subscription-page', 'upgrade') - eventTracking.sendMB('upgrade-button-click', { - source: 'subscription-page', - }) - } - } -) diff --git a/services/web/frontend/js/main/user-membership.js b/services/web/frontend/js/main/user-membership.js deleted file mode 100644 index e3e889515d..0000000000 --- a/services/web/frontend/js/main/user-membership.js +++ /dev/null @@ -1,126 +0,0 @@ -import _ from 'lodash' -/* eslint-disable - max-len, - no-return-assign, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -import App from '../base' -App.controller('UserMembershipController', function ($scope, queuedHttp) { - $scope.users = window.users - $scope.groupSize = window.groupSize - $scope.paths = window.paths - $scope.selectedUsers = [] - - $scope.inputs = { - addMembers: { - content: '', - error: false, - errorMessage: null, - inflightCount: 0, - }, - removeMembers: { - error: false, - errorMessage: null, - }, - } - - const parseEmails = function (emailsString) { - const regexBySpaceOrComma = /[\s,]+/ - let emails = emailsString.split(regexBySpaceOrComma) - emails = _.map(emails, email => (email = email.trim())) - emails = _.filter(emails, email => email.indexOf('@') !== -1) - return emails - } - - $scope.addMembers = function () { - $scope.inputs.addMembers.error = false - $scope.inputs.addMembers.errorMessage = null - $scope.inputs.addMembers.inflightCount = 0 - const emails = parseEmails($scope.inputs.addMembers.content) - return Array.from(emails).map(email => { - $scope.inputs.addMembers.inflightCount += 1 - return queuedHttp - .post(window.paths.addMember, { - email, - _csrf: window.csrfToken, - }) - .then(function (response) { - $scope.inputs.addMembers.inflightCount -= 1 - const { data } = response - if (data.user != null) { - const alreadyListed = $scope.users.find( - scopeUser => scopeUser.email === data.user.email - ) - if (!alreadyListed) { - $scope.users.push(data.user) - } - } - return ($scope.inputs.addMembers.content = '') - }) - .catch(function (response) { - $scope.inputs.addMembers.inflightCount -= 1 - const { data } = response - $scope.inputs.addMembers.error = true - return ($scope.inputs.addMembers.errorMessage = - data.error != null ? data.error.message : undefined) - }) - }) - } - - $scope.removeMembers = function () { - $scope.inputs.removeMembers.error = false - $scope.inputs.removeMembers.errorMessage = null - for (const user of Array.from($scope.selectedUsers)) { - ;(function (user) { - let url - if (window.paths.removeInvite && user.invite && user._id == null) { - url = `${window.paths.removeInvite}/${encodeURIComponent(user.email)}` - } else if (window.paths.removeMember && user._id != null) { - url = `${window.paths.removeMember}/${user._id}` - } else { - return - } - return queuedHttp({ - method: 'DELETE', - url, - headers: { - 'X-Csrf-Token': window.csrfToken, - }, - }) - .then(function () { - const index = $scope.users.indexOf(user) - if (index === -1) { - return - } - return $scope.users.splice(index, 1) - }) - .catch(function (response) { - const { data } = response - $scope.inputs.removeMembers.error = true - return ($scope.inputs.removeMembers.errorMessage = - data.error != null ? data.error.message : undefined) - }) - })(user) - } - return $scope.updateSelectedUsers - } - - return ($scope.updateSelectedUsers = () => - ($scope.selectedUsers = $scope.users.filter(user => user.selected))) -}) - -export default App.controller('UserMembershipListItemController', $scope => - $scope.$watch('user.selected', function (value) { - if (value != null) { - return $scope.updateSelectedUsers() - } - }) -) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index f1028dc507..3de0be487e 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -272,7 +272,6 @@ "contact_message_label": "Message", "contact_sales": "Contact Sales", "contact_support_to_change_group_subscription": "Please <0>contact support if you wish to change your group subscription.", - "contact_support_to_upgrade_to_group_subscription": "Please <0>contact support if you wish to be upgraded to a group subscription.", "contact_us": "Contact Us", "contact_us_lowercase": "Contact us", "continue": "Continue", @@ -1476,7 +1475,6 @@ "stop_on_first_error_enabled_description": "<0>“Stop on first error” is enabled. Disabling it may allow the compiler to produce a PDF (but your project will still have errors).", "stop_on_first_error_enabled_title": "No PDF: Stop on first error enabled", "stop_on_validation_error": "Check syntax before compile", - "stop_your_subscription": "Stop Your Subscription", "store_your_work": "Store your work on your own infrastructure", "student": "Student", "student_and_faculty_support_make_difference": "Student and faculty support make a difference! We can share this information with our contacts at your university when discussing an Overleaf institutional account.", diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 33936edd0a..60739e2ed6 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -78,6 +78,9 @@ describe('SubscriptionController', function () { promises: { buildUsersSubscriptionViewModel: sinon.stub().resolves({}), }, + buildPlansListForSubscriptionDash: sinon + .stub() + .returns({ plans: [], planCodesChangingAtTermEnd: [] }), } this.settings = { coupon_codes: { @@ -283,10 +286,10 @@ describe('SubscriptionController', function () { describe('with a valid plan code', function () { it('should render the new subscription page', function (done) { this.res.render = (page, opts) => { - page.should.equal('subscriptions/new-refreshed') + page.should.equal('subscriptions/new-react') done() } - this.SubscriptionController.paymentPage(this.req, this.res) + this.SubscriptionController.paymentPage(this.req, this.res, done) }) }) }) @@ -407,7 +410,7 @@ describe('SubscriptionController', function () { } ) this.res.render = (url, variables) => { - url.should.equal('subscriptions/successful-subscription') + url.should.equal('subscriptions/successful-subscription-react') assert.deepEqual(variables, { title: 'thank_you', personalSubscription: 'foo', @@ -448,13 +451,19 @@ describe('SubscriptionController', function () { this.SubscriptionViewModelBuilder.buildPlansList.returns( (this.plans = { plans: 'mock' }) ) + this.SubscriptionViewModelBuilder.buildPlansListForSubscriptionDash.returns( + { + plans: this.plans, + planCodesChangingAtTermEnd: [], + } + ) this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(false) this.res.render = (view, data) => { this.data = data - expect(view).to.equal('subscriptions/dashboard') + expect(view).to.equal('subscriptions/dashboard-react') done() } - this.SubscriptionController.userSubscriptionPage(this.req, this.res) + this.SubscriptionController.userSubscriptionPage(this.req, this.res, done) }) it('should load the personal, groups and v1 subscriptions', function () { diff --git a/services/web/test/unit/src/UserMembership/UserMembershipControllerTests.js b/services/web/test/unit/src/UserMembership/UserMembershipControllerTests.js index d288339f05..a261161fc4 100644 --- a/services/web/test/unit/src/UserMembership/UserMembershipControllerTests.js +++ b/services/web/test/unit/src/UserMembership/UserMembershipControllerTests.js @@ -110,13 +110,9 @@ describe('UserMembershipController', function () { it('render group view', async function () { return await this.UserMembershipController.manageGroupMembers(this.req, { render: (viewPath, viewParams) => { - expect(viewPath).to.equal('user_membership/index') + expect(viewPath).to.equal('user_membership/group-members-react') expect(viewParams.users).to.deep.equal(this.users) expect(viewParams.groupSize).to.equal(this.subscription.membersLimit) - expect(viewParams.translations.title).to.equal('group_subscription') - expect(viewParams.paths.addMember).to.equal( - `/manage/groups/${this.subscription._id}/invites` - ) }, }) }) @@ -125,13 +121,8 @@ describe('UserMembershipController', function () { this.req.entityConfig = EntityConfigs.groupManagers return await this.UserMembershipController.manageGroupManagers(this.req, { render: (viewPath, viewParams) => { - expect(viewPath).to.equal('user_membership/index') + expect(viewPath).to.equal('user_membership/group-managers-react') expect(viewParams.groupSize).to.equal(undefined) - expect(viewParams.translations.title).to.equal('group_subscription') - expect(viewParams.translations.subtitle).to.equal( - 'managers_management' - ) - expect(viewParams.paths.exportMembers).to.be.undefined }, }) }) @@ -143,13 +134,11 @@ describe('UserMembershipController', function () { this.req, { render: (viewPath, viewParams) => { - expect(viewPath).to.equal('user_membership/index') + expect(viewPath).to.equal( + 'user_membership/institution-managers-react' + ) expect(viewParams.name).to.equal('Test Institution Name') expect(viewParams.groupSize).to.equal(undefined) - expect(viewParams.translations.title).to.equal( - 'institution_account' - ) - expect(viewParams.paths.exportMembers).to.be.undefined }, } )