From db64829f63ef08e31bd41a35e66d254e7067d58a Mon Sep 17 00:00:00 2001 From: M Fahru Date: Wed, 15 Jan 2025 10:05:44 -0700 Subject: [PATCH] Merge pull request #22742 from overleaf/mf-remove-frontend-plans-page-dead-code [web] Remove frontend plans page dead code GitOrigin-RevId: 6db07b909f99a7afd17880698787a2c3527e879f --- .../features/plans/group-plan-modal/index.js | 162 --------- .../plans/plans-v2-group-plan-modal.js | 74 ---- .../plans/utils/group-plan-pricing.js | 43 --- .../plans-v2/plans-v2-group-plan.js | 142 -------- .../subscription/plans-v2/plans-v2-hash.js | 52 --- .../plans-v2/plans-v2-m-a-switch.js | 63 ---- .../subscription/plans-v2/plans-v2-main.js | 336 ------------------ .../plans-v2/plans-v2-sticky-header.js | 17 - .../plans-v2/plans-v2-subscription-button.js | 32 -- .../plans-v2/plans-v2-tracking.ts | 51 --- .../shared/utils/group-plan-pricing.test.js | 77 ---- 11 files changed, 1049 deletions(-) delete mode 100644 services/web/frontend/js/features/plans/group-plan-modal/index.js delete mode 100644 services/web/frontend/js/features/plans/plans-v2-group-plan-modal.js delete mode 100644 services/web/frontend/js/features/plans/utils/group-plan-pricing.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-group-plan.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-hash.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-m-a-switch.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-main.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-sticky-header.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-subscription-button.js delete mode 100644 services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-tracking.ts delete mode 100644 services/web/test/frontend/shared/utils/group-plan-pricing.test.js diff --git a/services/web/frontend/js/features/plans/group-plan-modal/index.js b/services/web/frontend/js/features/plans/group-plan-modal/index.js deleted file mode 100644 index 32858d295b..0000000000 --- a/services/web/frontend/js/features/plans/group-plan-modal/index.js +++ /dev/null @@ -1,162 +0,0 @@ -import getMeta from '../../../utils/meta' -import { swapModal } from '../../utils/swapModal' -import * as eventTracking from '../../../infrastructure/event-tracking' -import { createLocalizedGroupPlanPrice } from '../utils/group-plan-pricing' - -export const GROUP_PLAN_MODAL_HASH = '#groups' - -function getFormValues() { - const modalEl = document.querySelector('[data-ol-group-plan-modal]') - const planCode = modalEl.querySelector( - 'input[name="plan_code"]:checked' - ).value - const size = modalEl.querySelector('#size').value - const currency = modalEl.querySelector('#currency').value - const usage = modalEl.querySelector('#usage').checked - ? 'educational' - : 'enterprise' - return { planCode, size, currency, usage } -} - -export function updateGroupModalPlanPricing() { - const modalEl = document.querySelector('[data-ol-group-plan-modal]') - const { planCode, size, currency, usage } = getFormValues() - - const { localizedPrice, localizedPerUserPrice } = - createLocalizedGroupPlanPrice({ - plan: planCode, - licenseSize: size, - currency, - usage, - }) - - modalEl.querySelectorAll('[data-ol-group-plan-plan-code]').forEach(el => { - el.hidden = el.getAttribute('data-ol-group-plan-plan-code') !== planCode - }) - modalEl.querySelectorAll('[data-ol-group-plan-usage]').forEach(el => { - el.hidden = el.getAttribute('data-ol-group-plan-usage') !== usage - }) - modalEl.querySelector('[data-ol-group-plan-display-price]').innerText = - localizedPrice - modalEl - .querySelectorAll('[data-ol-group-plan-price-per-user]') - .forEach(el => { - el.innerText = `${localizedPerUserPrice} ${el.getAttribute( - 'data-ol-group-plan-price-per-user' - )}` - }) - - modalEl.querySelector('[data-ol-group-plan-educational-discount]').hidden = - usage !== 'educational' - - modalEl.querySelector( - '[data-ol-group-plan-educational-discount-applied]' - ).hidden = size < 10 - - modalEl.querySelector( - '[data-ol-group-plan-educational-discount-ineligible]' - ).hidden = size >= 10 -} - -const modalEl = $('[data-ol-group-plan-modal]') -modalEl - .on('shown.bs.modal', function () { - const path = `${window.location.pathname}${window.location.search}` - history.replaceState(null, document.title, path + GROUP_PLAN_MODAL_HASH) - eventTracking.sendMB('form-submitted-groups-modal-open') - }) - .on('hidden.bs.modal', function () { - const path = `${window.location.pathname}${window.location.search}${window.location.hash}` - history.replaceState(null, document.title, path) - }) - -function showGroupPlanModal() { - modalEl.modal() - eventTracking.send( - 'subscription-funnel', - 'plans-page', - 'group-inquiry-potential' - ) // deprecated by plans-page-click -} - -document - .querySelectorAll('[data-ol-group-plan-form] select') - .forEach(el => el.addEventListener('change', updateGroupModalPlanPricing)) -document - .querySelectorAll('[data-ol-group-plan-form] input') - .forEach(el => el.addEventListener('change', updateGroupModalPlanPricing)) -document.querySelectorAll('[data-ol-purchase-group-plan]').forEach(el => - el.addEventListener('click', e => { - e.preventDefault() - - const { planCode, size, currency, usage } = getFormValues() - const queryParams = new URLSearchParams( - Object.entries({ - planCode: `group_${planCode}_${size}_${usage}`, - currency, - itm_campaign: 'groups', - }) - ) - const itmContent = getMeta('ol-itm_content') - if (itmContent) { - queryParams.set('itm_content', itmContent) - } - eventTracking.sendMB('groups-modal-click', { - plan: planCode, - users: size, - currency, - type: usage, - }) - const url = new URL('/user/subscription/new', window.origin) - url.search = queryParams.toString() - window.location = url.toString() - }) -) - -document.querySelectorAll('[data-ol-open-group-plan-modal]').forEach(el => { - const location = el.getAttribute('data-ol-location') - el.addEventListener('click', function (e) { - e.preventDefault() - eventTracking.sendMB('plans-page-click', { - button: 'group', - location, - 'billing-period': 'annual', - }) - showGroupPlanModal() - }) -}) - -document - .querySelectorAll('[data-ol-open-contact-form-for-more-than-50-licenses]') - .forEach(el => { - el.addEventListener('click', function (e) { - e.preventDefault() - swapModal( - '[data-ol-group-plan-modal]', - '[data-ol-contact-form-modal="general"]' - ) - }) - }) - -function updateGroupModalPlanPricingIfAvailable() { - const isGroupPlanModalAvailable = document.querySelector( - '[data-ol-group-plan-modal]' - ) - - if (isGroupPlanModalAvailable) { - updateGroupModalPlanPricing() - } -} - -updateGroupModalPlanPricingIfAvailable() - -// When using browser back buttons, we need to update the pricing plan -// after the page has fully loaded as we need to wait for the previously -// selected values to load for e.g. size. -window.addEventListener('load', () => { - updateGroupModalPlanPricingIfAvailable() -}) - -if (window.location.hash === GROUP_PLAN_MODAL_HASH) { - showGroupPlanModal() -} diff --git a/services/web/frontend/js/features/plans/plans-v2-group-plan-modal.js b/services/web/frontend/js/features/plans/plans-v2-group-plan-modal.js deleted file mode 100644 index 8655b1a43c..0000000000 --- a/services/web/frontend/js/features/plans/plans-v2-group-plan-modal.js +++ /dev/null @@ -1,74 +0,0 @@ -import './group-plan-modal' -import { updateMainGroupPlanPricing } from '../../pages/user/subscription/plans-v2/plans-v2-group-plan' - -export function changePlansV2MainPageGroupData() { - const mainPlansPageFormEl = document.querySelector( - '[data-ol-plans-v2-license-picker-form]' - ) - const mainPlansPageLicensePickerEl = mainPlansPageFormEl.querySelector( - '[data-ol-plans-v2-license-picker-select]' - ) - - const mainPlansPageEducationalDiscountEl = mainPlansPageFormEl.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ) - - const groupPlanModalNumberOfLicenses = document.querySelector( - '[data-ol-group-plan-modal] #size' - ).value - - const educationalDiscountChecked = document.querySelector( - '[data-ol-group-plan-modal] #usage' - ).checked - - const educationalDiscountEnabled = - educationalDiscountChecked && groupPlanModalNumberOfLicenses >= 10 - - // update license picker on the main plans page - mainPlansPageLicensePickerEl.value = groupPlanModalNumberOfLicenses - - // update educational discount checkbox on the main plans page - // - // extra note - // for number of users < 10, there is a difference on the checkbox behaviour - // between the group plan modal and the main plan page - // - // On the group plan modal, the checkbox button is not visually disabled for number of users < 10 (checkbox can still be clicked) - // but the logic is disabled and there will be an extra text whether or not the discount is applied - // - // However, on the main group plan page, the checkbox button is visually disabled for number of users < 10 (checkbox can not be clicked) - // Hence, there's a possibility that the checkbox on the group plan modal is checked, but the discount is not applied. - // i.e user can still click the checkbox with number of users < 10. The price won't be discounted, but the checkbox is checked. - if (groupPlanModalNumberOfLicenses >= 10) { - mainPlansPageEducationalDiscountEl.checked = educationalDiscountEnabled - } else { - // The code below is for disabling the checkbox button on the main plan page for number of users <10 - // while still checking the educational discount - if (educationalDiscountChecked) { - mainPlansPageEducationalDiscountEl.checked = false - } - } - - updateMainGroupPlanPricing() -} - -function hideCurrencyPicker() { - document.querySelector('[data-ol-group-plan-form-currency]').hidden = true -} - -document.querySelectorAll('[data-ol-group-plan-form] select').forEach(el => - el.addEventListener('change', () => { - changePlansV2MainPageGroupData() - }) -) -document - .querySelectorAll('[data-ol-group-plan-form] input') - .forEach(el => el.addEventListener('change', changePlansV2MainPageGroupData)) - -const isGroupPlanModalAvailable = document.querySelector( - '[data-ol-group-plan-modal]' -) - -if (isGroupPlanModalAvailable) { - hideCurrencyPicker() -} diff --git a/services/web/frontend/js/features/plans/utils/group-plan-pricing.js b/services/web/frontend/js/features/plans/utils/group-plan-pricing.js deleted file mode 100644 index 060a8ea2a7..0000000000 --- a/services/web/frontend/js/features/plans/utils/group-plan-pricing.js +++ /dev/null @@ -1,43 +0,0 @@ -import { formatCurrency } from '@/shared/utils/currency' -import getMeta from '../../../utils/meta' - -/** - * @import { CurrencyCode } from '../../../../../types/subscription/currency' - */ - -// plan: 'collaborator' or 'professional' -// the rest of available arguments can be seen in the groupPlans value -/** - * @param {Object} opts - * @param {'collaborator' | 'professional'} opts.plan - * @param {string} opts.licenseSize - * @param {CurrencyCode} opts.currency - * @param {'enterprise' | 'educational'} opts.usage - * @param {string} [opts.locale] - * @returns {{localizedPrice: string, localizedPerUserPrice: string}} - */ -export function createLocalizedGroupPlanPrice({ - plan, - licenseSize, - currency, - usage, - locale = getMeta('ol-i18n').currentLangCode || 'en', -}) { - const groupPlans = getMeta('ol-groupPlans') - const priceInCents = - groupPlans[usage][plan][currency][licenseSize].price_in_cents - - const price = priceInCents / 100 - const perUserPrice = price / parseInt(licenseSize) - - /** - * @param {number} price - * @returns {string} - */ - const formatPrice = price => formatCurrency(price, currency, locale, true) - - return { - localizedPrice: formatPrice(price), - localizedPerUserPrice: formatPrice(perUserPrice), - } -} diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-group-plan.js b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-group-plan.js deleted file mode 100644 index 5616d40fa8..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-group-plan.js +++ /dev/null @@ -1,142 +0,0 @@ -import '../../../../features/plans/plans-v2-group-plan-modal' - -import getMeta from '../../../../utils/meta' -import { updateGroupModalPlanPricing } from '../../../../features/plans/group-plan-modal' -import { createLocalizedGroupPlanPrice } from '../../../../features/plans/utils/group-plan-pricing' - -const MINIMUM_LICENSE_SIZE_EDUCATIONAL_DISCOUNT = 10 - -export function updateMainGroupPlanPricing() { - const currency = getMeta('ol-recommendedCurrency') - - const formEl = document.querySelector( - '[data-ol-plans-v2-license-picker-form]' - ) - const licenseSize = formEl.querySelector( - '[data-ol-plans-v2-license-picker-select]' - ).value - - const usage = formEl.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ).checked - ? 'educational' - : 'enterprise' - - const { - localizedPrice: localizedPriceProfessional, - localizedPerUserPrice: localizedPerUserPriceProfessional, - } = createLocalizedGroupPlanPrice({ - plan: 'professional', - licenseSize, - currency, - usage, - }) - - const { - localizedPrice: localizedPriceCollaborator, - localizedPerUserPrice: localizedPerUserPriceCollaborator, - } = createLocalizedGroupPlanPrice({ - plan: 'collaborator', - licenseSize, - currency, - usage, - }) - - document.querySelector( - '[data-ol-plans-v2-group-total-price="professional"]' - ).innerText = localizedPriceProfessional - - document.querySelector( - '[data-ol-plans-v2-group-price-per-user="professional"]' - ).innerText = localizedPerUserPriceProfessional - - document.querySelector( - '[data-ol-plans-v2-group-total-price="collaborator"]' - ).innerText = localizedPriceCollaborator - - document.querySelector( - '[data-ol-plans-v2-group-price-per-user="collaborator"]' - ).innerText = localizedPerUserPriceCollaborator - - const notEligibleForEducationalDiscount = - licenseSize < MINIMUM_LICENSE_SIZE_EDUCATIONAL_DISCOUNT - - formEl - .querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-label]' - ) - .classList.toggle('disabled', notEligibleForEducationalDiscount) - - formEl - .querySelector('.plans-v2-license-picker-educational-discount') - .classList.toggle( - 'total-licenses-not-eligible-for-discount', - notEligibleForEducationalDiscount - ) - - formEl.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ).disabled = notEligibleForEducationalDiscount - - if (notEligibleForEducationalDiscount) { - // force disable educational discount checkbox - formEl.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ).checked = false - } - - changeNumberOfUsersInTableHead() - changeNumberOfUsersInFeatureTable() -} - -export function changeGroupPlanModalNumberOfLicenses() { - const modalEl = document.querySelector('[data-ol-group-plan-modal]') - const numberOfLicenses = document.querySelector( - '[data-ol-plans-v2-license-picker-select]' - ).value - - const groupPlanModalLicensePickerEl = modalEl.querySelector('#size') - - groupPlanModalLicensePickerEl.value = numberOfLicenses - updateGroupModalPlanPricing() -} - -export function changeGroupPlanModalEducationalDiscount() { - const modalEl = document.querySelector('[data-ol-group-plan-modal]') - const groupPlanModalEducationalDiscountEl = modalEl.querySelector('#usage') - const educationalDiscountChecked = document.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ).checked - - groupPlanModalEducationalDiscountEl.checked = educationalDiscountChecked - updateGroupModalPlanPricing() -} - -export function changeNumberOfUsersInFeatureTable() { - document - .querySelectorAll( - '[data-ol-plans-v2-table-cell-plan^="group"][data-ol-plans-v2-table-cell-feature="number_of_users"]' - ) - .forEach(el => { - const licenseSize = document.querySelector( - '[data-ol-plans-v2-license-picker-select]' - ).value - - el.textContent = el.textContent.replace(/\d+/, licenseSize) - }) -} - -export function changeNumberOfUsersInTableHead() { - document - .querySelectorAll('[data-ol-plans-v2-table-th-group-license-size]') - .forEach(el => { - const licenseSize = el.getAttribute( - 'data-ol-plans-v2-table-th-group-license-size' - ) - const currentLicenseSize = document.querySelector( - '[data-ol-plans-v2-license-picker-select]' - ).value - - el.hidden = licenseSize !== currentLicenseSize - }) -} diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-hash.js b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-hash.js deleted file mode 100644 index 2321f83db8..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-hash.js +++ /dev/null @@ -1,52 +0,0 @@ -import { GROUP_PLAN_MODAL_HASH } from '@/features/plans/group-plan-modal' - -export function getViewInfoFromHash() { - const hashValue = window.location.hash.replace('#', '') - - const groupPlanModalHashValue = GROUP_PLAN_MODAL_HASH.replace('#', '') - - switch (hashValue) { - case 'individual-monthly': - return ['individual', 'monthly'] - case 'individual-annual': - return ['individual', 'annual'] - case groupPlanModalHashValue: - case 'group': - return ['group', 'annual'] - case 'student-monthly': - return ['student', 'monthly'] - case 'student-annual': - return ['student', 'annual'] - default: - return ['individual', 'monthly'] - } -} - -/** - * - * @param {individual | group | student} viewTab - * @param {monthly | annual} period - */ -export function setHashFromViewTab(viewTab, period) { - const newHash = viewTab === 'group' ? 'group' : `${viewTab}-${period}` - if (window.location.hash.replace('#', '') !== newHash) { - window.location.hash = newHash - } -} - -// this is only for the students link in footer -export function handleForStudentsLinkInFooter() { - const links = document.querySelectorAll('[data-ol-for-students-link]') - - links.forEach(function (link) { - link.addEventListener('click', function () { - if (window.location.pathname === '/user/subscription/plans') { - // reload location with the correct hash - const newURL = - '/user/subscription/plans?itm_referrer=footer-for-students#student-annual' - history.replaceState(null, '', newURL) - location.reload() - } - }) - }) -} diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-m-a-switch.js b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-m-a-switch.js deleted file mode 100644 index faa30804b8..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-m-a-switch.js +++ /dev/null @@ -1,63 +0,0 @@ -// m-a stands for monthly-annual - -export function toggleMonthlyAnnualSwitching( - view, - currentMonthlyAnnualSwitchValue -) { - const containerEl = document.querySelector( - '[data-ol-plans-v2-m-a-switch-container]' - ) - if (containerEl) { - const checkbox = containerEl.querySelector('input[type="checkbox"]') - - containerEl.classList.toggle('disabled', view === 'group') - - checkbox.disabled = view === 'group' - checkbox.checked = currentMonthlyAnnualSwitchValue === 'monthly' - - switchMonthlyAnnual(currentMonthlyAnnualSwitchValue) - } -} - -export function switchMonthlyAnnual(currentMonthlyAnnualSwitchValue) { - const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - el.classList.toggle( - 'plans-v2-m-a-tooltip-monthly-selected', - currentMonthlyAnnualSwitchValue === 'monthly' - ) - - document.querySelectorAll('[data-ol-tooltip-period]').forEach(el => { - const period = el.getAttribute('data-ol-tooltip-period') - el.hidden = period !== currentMonthlyAnnualSwitchValue - }) - - document.querySelectorAll('[data-ol-plans-v2-period').forEach(el => { - const period = el.getAttribute('data-ol-plans-v2-period') - - el.hidden = currentMonthlyAnnualSwitchValue !== period - }) - - document - .querySelectorAll('[data-ol-plans-v2-m-a-switch-text]') - .forEach(el => { - el.classList.toggle( - 'underline', - el.getAttribute('data-ol-plans-v2-m-a-switch-text') === - currentMonthlyAnnualSwitchValue - ) - }) -} - -function changeMonthlyAnnualTooltipPosition() { - const smallScreen = window.matchMedia('(max-width: 767px)').matches - const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - - el.classList.toggle('bottom', smallScreen) - el.classList.toggle('left', !smallScreen) -} - -// click event listener for monthly-annual switch -export function setUpMonthlyAnnualSwitching() { - changeMonthlyAnnualTooltipPosition() - window.addEventListener('resize', changeMonthlyAnnualTooltipPosition) -} diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-main.js b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-main.js deleted file mode 100644 index d3465bee1d..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-main.js +++ /dev/null @@ -1,336 +0,0 @@ -import '../../../../marketing' -import '../../../../infrastructure/hotjar' - -import * as eventTracking from '../../../../infrastructure/event-tracking' -import { setUpStickyHeaderObserver } from './plans-v2-sticky-header' -import { - setUpMonthlyAnnualSwitching, - switchMonthlyAnnual, - toggleMonthlyAnnualSwitching, -} from './plans-v2-m-a-switch' -import { - changeGroupPlanModalEducationalDiscount, - changeGroupPlanModalNumberOfLicenses, - updateMainGroupPlanPricing, -} from './plans-v2-group-plan' -import { setUpGroupSubscriptionButtonAction } from './plans-v2-subscription-button' -import { - getViewInfoFromHash, - handleForStudentsLinkInFooter, - setHashFromViewTab, -} from './plans-v2-hash' -import { sendPlansViewEvent } from './plans-v2-tracking' -import getMeta from '../../../../utils/meta' - -const currentCurrencyCode = getMeta('ol-recommendedCurrency') - -function showQuoteForTab(viewTab) { - // hide/display quote rows - document.querySelectorAll('.plans-page-quote-row').forEach(quoteRow => { - const showForPlanTypes = quoteRow.getAttribute('data-ol-show-for-plan-type') - if (showForPlanTypes?.includes(viewTab)) { - quoteRow.classList.remove('plans-page-quote-row-hidden') - } else { - quoteRow.classList.add('plans-page-quote-row-hidden') - } - }) -} - -function setUpSubscriptionTracking(linkEl) { - linkEl.addEventListener('click', function () { - const plan = - linkEl.getAttribute('data-ol-tracking-plan') || - linkEl.getAttribute('data-ol-start-new-subscription') - - const location = linkEl.getAttribute('data-ol-location') - const period = linkEl.getAttribute('data-ol-item-view') - - const DEFAULT_EVENT_TRACKING_KEY = 'plans-page-click' - - const eventTrackingKey = - linkEl.getAttribute('data-ol-event-tracking-key') || - DEFAULT_EVENT_TRACKING_KEY - - const eventTrackingSegmentation = { - button: plan, - location, - 'billing-period': period, - currency: currentCurrencyCode, - } - - eventTracking.sendMB(eventTrackingKey, eventTrackingSegmentation) - }) -} - -const searchParams = new URLSearchParams(window.location.search) - -export function updateLinkTargets() { - document.querySelectorAll('[data-ol-start-new-subscription]').forEach(el => { - if (el.hasAttribute('data-ol-has-custom-href')) return - - const plan = el.getAttribute('data-ol-start-new-subscription') - const view = el.getAttribute('data-ol-item-view') - - const suffix = view === 'annual' ? '-annual' : '_free_trial_7_days' - - const planCode = `${plan}${suffix}` - - const location = el.getAttribute('data-ol-location') - const itmCampaign = searchParams.get('itm_campaign') || 'plans' - const itmContent = - itmCampaign === 'plans' ? location : searchParams.get('itm_content') - - const queryString = new URLSearchParams({ - planCode, - currency: currentCurrencyCode, - itm_campaign: itmCampaign, - }) - - if (itmContent) { - queryString.set('itm_content', itmContent) - } - - if (searchParams.get('itm_referrer')) { - queryString.set('itm_referrer', searchParams.get('itm_referrer')) - } - - el.href = `/user/subscription/new?${queryString.toString()}` - }) -} - -// We need this mutable variable because the group tab only have annual. -// There's some difference between the monthly and annual UI -// and since monthly-annual switch is disabled for the group tab, -// we need to introduce a new variable to store the information -let currentMonthlyAnnualSwitchValue = 'annual' - -function selectTab(viewTab) { - document.querySelectorAll('[data-ol-plans-v2-view-tab]').forEach(el => { - const tab = el.querySelector('[data-ol-plans-v2-view-tab] button') - if (tab) { - const isActive = - tab.parentElement.getAttribute('data-ol-plans-v2-view-tab') === viewTab - tab.parentElement.classList.toggle('active', isActive) - tab.setAttribute('aria-selected', isActive) - } - }) - - document.querySelectorAll('[data-ol-plans-v2-view]').forEach(el => { - el.hidden = el.getAttribute('data-ol-plans-v2-view') !== viewTab - }) - - const tooltipEl = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - if (tooltipEl) { - tooltipEl.hidden = viewTab === 'group' - } - - const licensePickerEl = document.querySelector( - '[data-ol-plans-v2-license-picker-container]' - ) - if (licensePickerEl) { - licensePickerEl.hidden = viewTab !== 'group' - } - - const monthlyAnnualSwitch = document.querySelector( - '[data-ol-plans-v2-m-a-switch-container]' - ) - if (monthlyAnnualSwitch) { - monthlyAnnualSwitch.setAttribute('data-ol-current-view', viewTab) - } - - if (viewTab === 'group') { - updateMainGroupPlanPricing() - } - - updateMonthlyAnnualSwitchValue(viewTab) - - toggleUniversityInfo(viewTab) - - // update the hash to reflect the current view when switching individual, group, or student tabs - setHashFromViewTab(viewTab, currentMonthlyAnnualSwitchValue) - - showQuoteForTab(viewTab) -} - -function updateMonthlyAnnualSwitchValue(viewTab) { - // group tab is special because group plan only has annual value - // so we need to perform some UI changes whenever user click the group tab - if (viewTab === 'group') { - toggleMonthlyAnnualSwitching(viewTab, 'annual') - } else { - toggleMonthlyAnnualSwitching(viewTab, currentMonthlyAnnualSwitchValue) - } -} - -function setUpTabSwitching() { - document.querySelectorAll('[data-ol-plans-v2-view-tab]').forEach(el => { - const viewTab = el.getAttribute('data-ol-plans-v2-view-tab') - - el.querySelector('button').addEventListener('click', function (e) { - e.preventDefault() - eventTracking.send( - 'subscription-funnel', - 'plans-page', - `${viewTab}-prices` - ) - selectTab(viewTab) - }) - }) - - const tabs = document.querySelectorAll( - '[data-ol-plans-v2-view-tab] [role="tab"]' - ) - - if (tabs) { - tabs.forEach(tab => { - tab.addEventListener('keydown', event => { - if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { - const currentIndex = Array.from(tabs).indexOf(tab) - const nextIndex = - event.key === 'ArrowLeft' ? currentIndex - 1 : currentIndex + 1 - const newIndex = (nextIndex + tabs.length) % tabs.length - tabs[newIndex].focus() - } - }) - }) - } -} - -function setUpGroupPlanPricingChange() { - document - .querySelectorAll('[data-ol-plans-v2-license-picker-select]') - .forEach(el => { - el.addEventListener('change', () => { - updateMainGroupPlanPricing() - changeGroupPlanModalNumberOfLicenses() - }) - }) - - document - .querySelectorAll( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ) - .forEach(el => - el.addEventListener('change', () => { - updateMainGroupPlanPricing() - changeGroupPlanModalEducationalDiscount() - }) - ) -} - -function toggleUniversityInfo(viewTab) { - const el = document.querySelector('[data-ol-plans-university-info-container]') - if (el) { - el.hidden = viewTab !== 'student' - } -} - -// This is the old scheme for hashing redirection -// This is deprecated and should be removed in the future -// This is only used for backward compatibility -function selectViewFromHashDeprecated() { - try { - const params = new URLSearchParams(window.location.hash.substring(1)) - const view = params.get('view') - if (view) { - // View params are expected to be of the format e.g. individual or individual-monthly - const [tab, period] = view.split('-') - // make sure the selected view is valid - if (document.querySelector(`[data-ol-plans-v2-view-tab="${tab}"]`)) { - selectTab(tab) - - if (['monthly', 'annual'].includes(period)) { - currentMonthlyAnnualSwitchValue = period - } else { - // set annual as the default - currentMonthlyAnnualSwitchValue = 'annual' - } - - updateMonthlyAnnualSwitchValue(tab) - - // change the hash with the new scheme - setHashFromViewTab(tab, currentMonthlyAnnualSwitchValue) - } - } - } catch { - // do nothing - } -} - -function selectViewAndPeriodFromHash() { - const [viewTab, period] = getViewInfoFromHash() - - // the sequence of these three lines is important - // because `currentMonthlyAnnualSwitchValue` is mutable. - // `selectTab` and `updateMonthlyAnnualSwitchValue` depend on the value of `currentMonthlyAnnualSwitchValue` - // to determine the UI state - currentMonthlyAnnualSwitchValue = period - selectTab(viewTab) - updateMonthlyAnnualSwitchValue(viewTab) - - // handle the case where user access plans page while still on the plans page - // current example would the the "For students" link on the footer - const SCROLL_TO_TOP_DELAY = 50 - window.setTimeout(() => { - window.scrollTo({ top: 0, behavior: 'smooth' }) - }, SCROLL_TO_TOP_DELAY) -} - -// call the function to select the view and period from the hash value -// this is called once when the page is loaded -if (window.location.hash) { - if (window.location.hash.includes('view')) { - selectViewFromHashDeprecated() - } else { - selectViewAndPeriodFromHash() - } -} - -document - .querySelector('[data-ol-plans-v2-m-a-switch]') - .addEventListener('click', () => { - const isMonthlyPricing = document.querySelector( - '[data-ol-plans-v2-m-a-switch] input[type="checkbox"]' - ).checked - - if (isMonthlyPricing) { - currentMonthlyAnnualSwitchValue = 'monthly' - } else { - currentMonthlyAnnualSwitchValue = 'annual' - } - - switchMonthlyAnnual(currentMonthlyAnnualSwitchValue) - - // update the hash to reflect the current view when pressing the monthly-annual switch - const DEFAULT_VIEW_TAB = 'individual' - const viewTab = - document - .querySelector('[data-ol-plans-v2-m-a-switch-container]') - .getAttribute('data-ol-current-view') ?? DEFAULT_VIEW_TAB - - setHashFromViewTab(viewTab, currentMonthlyAnnualSwitchValue) - }) - -document - .querySelectorAll('[data-ol-start-new-subscription]') - .forEach(setUpSubscriptionTracking) - -setUpTabSwitching() -setUpGroupPlanPricingChange() -setUpMonthlyAnnualSwitching() -setUpGroupSubscriptionButtonAction() -setUpStickyHeaderObserver() -updateLinkTargets() -handleForStudentsLinkInFooter() - -window.addEventListener('hashchange', () => { - if (window.location.hash) { - if (window.location.hash.includes('view')) { - selectViewFromHashDeprecated() - } else { - selectViewAndPeriodFromHash() - } - } -}) - -sendPlansViewEvent() diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-sticky-header.js b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-sticky-header.js deleted file mode 100644 index 47cd51605a..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-sticky-header.js +++ /dev/null @@ -1,17 +0,0 @@ -function stickyHeaderObserverCallback(entry) { - document - .querySelectorAll('[data-ol-plans-v2-table-sticky-header]') - .forEach(el => - el.classList.toggle('sticky', entry[0].boundingClientRect.bottom > 0) - ) -} - -export function setUpStickyHeaderObserver() { - const stickyHeaderStopEl = document.querySelector( - '[data-ol-plans-v2-table-sticky-header-stop]' - ) - - const observer = new IntersectionObserver(stickyHeaderObserverCallback) - - observer.observe(stickyHeaderStopEl) -} diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-subscription-button.js b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-subscription-button.js deleted file mode 100644 index 66ad71a74f..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-subscription-button.js +++ /dev/null @@ -1,32 +0,0 @@ -import { updateGroupModalPlanPricing } from '../../../../features/plans/group-plan-modal' - -function showGroupPlanModal(el) { - const plan = el.getAttribute('data-ol-start-new-subscription') - - // plan is either `group_collaborator` or `group_professional` - // we want to get the suffix (collaborator or professional) - const groupPlan = plan.split('_')[1] - - const groupModalRadioInputEl = document.querySelector( - `[data-ol-group-plan-code="${groupPlan}"]` - ) - - groupModalRadioInputEl.checked = true - updateGroupModalPlanPricing() - - const modalEl = $('[data-ol-group-plan-modal]') - modalEl.modal() -} - -export function setUpGroupSubscriptionButtonAction() { - document.querySelectorAll('[data-ol-start-new-subscription]').forEach(el => { - const plan = el.getAttribute('data-ol-start-new-subscription') - - if (plan === 'group_collaborator' || plan === 'group_professional') { - el.addEventListener('click', e => { - e.preventDefault() - showGroupPlanModal(el) - }) - } - }) -} diff --git a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-tracking.ts b/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-tracking.ts deleted file mode 100644 index 3f10f033ca..0000000000 --- a/services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-tracking.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { sendMB } from '@/infrastructure/event-tracking' -import { getSplitTestVariant } from '@/utils/splitTestUtils' -import getMeta from '@/utils/meta' - -export function sendPlansViewEvent() { - document.addEventListener( - 'DOMContentLoaded', - function () { - const currency = getMeta('ol-recommendedCurrency') - const countryCode = getMeta('ol-countryCode') - - const groupTabImprovementsVariant = getSplitTestVariant( - 'group-tab-improvements' - ) - - const periodToggleTestVariant = getSplitTestVariant( - 'period-toggle-improvements' - ) - - const device = window.matchMedia('(max-width: 767px)').matches - ? 'mobile' - : 'desktop' - - const queryParams = new URLSearchParams(window.location.search) - const planTabParam = queryParams.get('plan') - - const plansPageViewSegmentation = { - currency, - countryCode, - device, - 'group-tab-improvements': groupTabImprovementsVariant, - plan: planTabParam, - 'period-toggle-improvements': periodToggleTestVariant, - } - - const isPlansPage = window.location.href.includes( - 'user/subscription/plans' - ) - const isInterstitialPaymentPage = window.location.href.includes( - 'user/subscription/choose-your-plan' - ) - - if (isPlansPage) { - sendMB('plans-page-view', plansPageViewSegmentation) - } else if (isInterstitialPaymentPage) { - sendMB('paywall-plans-page-view', plansPageViewSegmentation) - } - }, - { once: true } - ) -} diff --git a/services/web/test/frontend/shared/utils/group-plan-pricing.test.js b/services/web/test/frontend/shared/utils/group-plan-pricing.test.js deleted file mode 100644 index d5d5f19de5..0000000000 --- a/services/web/test/frontend/shared/utils/group-plan-pricing.test.js +++ /dev/null @@ -1,77 +0,0 @@ -import { expect } from 'chai' -import { createLocalizedGroupPlanPrice } from '../../../../frontend/js/features/plans/utils/group-plan-pricing' - -describe('group-plan-pricing', function () { - beforeEach(function () { - window.metaAttributesCache.set('ol-groupPlans', { - enterprise: { - professional: { - CHF: { - 2: { - price_in_cents: 10000, - }, - }, - DKK: { - 2: { - price_in_cents: 20000, - }, - }, - USD: { - 2: { - price_in_cents: 30000, - }, - }, - }, - }, - }) - window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' }) - }) - - describe('createLocalizedGroupPlanPrice', function () { - describe('CHF currency', function () { - it('should return the correct localized price', function () { - const localizedGroupPlanPrice = createLocalizedGroupPlanPrice({ - plan: 'professional', - currency: 'CHF', - licenseSize: '2', - usage: 'enterprise', - }) - - expect(localizedGroupPlanPrice).to.deep.equal({ - localizedPrice: 'CHF 100', - localizedPerUserPrice: 'CHF 50', - }) - }) - }) - describe('DKK currency', function () { - it('should return the correct localized price', function () { - const localizedGroupPlanPrice = createLocalizedGroupPlanPrice({ - plan: 'professional', - currency: 'DKK', - licenseSize: '2', - usage: 'enterprise', - }) - - expect(localizedGroupPlanPrice).to.deep.equal({ - localizedPrice: 'kr 200', - localizedPerUserPrice: 'kr 100', - }) - }) - }) - describe('other supported currencies', function () { - it('should return the correct localized price', function () { - const localizedGroupPlanPrice = createLocalizedGroupPlanPrice({ - plan: 'professional', - currency: 'USD', - licenseSize: '2', - usage: 'enterprise', - }) - - expect(localizedGroupPlanPrice).to.deep.equal({ - localizedPrice: '$300', - localizedPerUserPrice: '$150', - }) - }) - }) - }) -})