diff --git a/services/web/app/views/subscriptions/plans-marketing/v2/_cards_controls_tables.pug b/services/web/app/views/subscriptions/plans-marketing/v2/_cards_controls_tables.pug index 7cdf0b9da0..0bdc704779 100644 --- a/services/web/app/views/subscriptions/plans-marketing/v2/_cards_controls_tables.pug +++ b/services/web/app/views/subscriptions/plans-marketing/v2/_cards_controls_tables.pug @@ -44,7 +44,7 @@ include ./_mixins +table_sticky_header -.row.plans-v2-table-container(data-ol-plans-v2-table-container='monthly') +.row.plans-v2-table-container(data-ol-plans-v2-period='monthly') .col-sm-12(data-ol-plans-v2-view='individual') .row +table_individual('monthly') @@ -52,7 +52,7 @@ include ./_mixins .row +table_student('monthly') -.row.plans-v2-table-container(hidden data-ol-plans-v2-table-container='annual') +.row.plans-v2-table-container(hidden data-ol-plans-v2-period='annual') .col-sm-12(data-ol-plans-v2-view='individual') .row +table_individual('annual') diff --git a/services/web/app/views/subscriptions/plans-marketing/v2/_mixins.pug b/services/web/app/views/subscriptions/plans-marketing/v2/_mixins.pug index cd39238e74..f5e693f147 100644 --- a/services/web/app/views/subscriptions/plans-marketing/v2/_mixins.pug +++ b/services/web/app/views/subscriptions/plans-marketing/v2/_mixins.pug @@ -455,7 +455,10 @@ mixin additional_link_buy(plan, period) ) #{translate("buy_now_no_exclamation_mark")} mixin table_sticky_header - .row.plans-v2-table-sticky-header.plans-v2-table-sticky-header-individual.sticky(data-ol-plans-v2-table-sticky-header='individual') + .row.plans-v2-table-sticky-header.plans-v2-table-sticky-header-individual.sticky( + data-ol-plans-v2-table-sticky-header + data-ol-plans-v2-view='individual' + ) .plans-v2-table-sticky-header-item span #{translate("free")} .plans-v2-table-sticky-header-item @@ -465,7 +468,11 @@ mixin table_sticky_header .plans-v2-table-sticky-header-item span #{translate("professional")} - .row.plans-v2-table-sticky-header.plans-v2-table-sticky-header-group.sticky(hidden data-ol-plans-v2-table-sticky-header='group') + .row.plans-v2-table-sticky-header.plans-v2-table-sticky-header-group.sticky( + hidden + data-ol-plans-v2-table-sticky-header + data-ol-plans-v2-view='group' + ) .plans-v2-table-sticky-header-item span #{translate("group_standard")} .plans-v2-table-sticky-header-item.plans-v2-table-sticky-header-item-highlighted @@ -473,7 +480,11 @@ mixin table_sticky_header .plans-v2-table-sticky-header-item span #{translate("organization")} - .row.plans-v2-table-sticky-header.plans-v2-table-sticky-header-student.sticky(hidden data-ol-plans-v2-table-sticky-header='student') + .row.plans-v2-table-sticky-header.plans-v2-table-sticky-header-student.sticky( + hidden + data-ol-plans-v2-table-sticky-header + data-ol-plans-v2-view='student' + ) .plans-v2-table-sticky-header-item span #{translate("free")} .plans-v2-table-sticky-header-item.plans-v2-table-sticky-header-item-highlighted @@ -484,7 +495,7 @@ mixin table_sticky_header mixin monthly_annual_switch(eventTracking, eventSegmentation) .row .col-md-4.col-md-offset-4.text-centered.plans-v2-m-a-switch-container(data-ol-plans-v2-m-a-switch-container) - span.underline(data-ol-plans-v2-m-a-switch-monthly-text) #{translate("monthly")} + span.underline(data-ol-plans-v2-m-a-switch-text='monthly') #{translate("monthly")} label.plans-v2-m-a-switch(data-ol-plans-v2-m-a-switch) input( type="checkbox" @@ -498,7 +509,7 @@ mixin monthly_annual_switch(eventTracking, eventSegmentation) ) span .plans-v2-m-a-switch-annual-text-container - span(data-ol-plans-v2-m-a-switch-annual-text) #{translate("annual")} + span(data-ol-plans-v2-m-a-switch-text='annual') #{translate("annual")} .tooltip.in.right.plans-v2-m-a-tooltip( role="tooltip" data-ol-plans-v2-m-a-tooltip 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 index 6079611578..c836eaaa67 100644 --- 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 @@ -23,21 +23,26 @@ export function updateMainGroupPlanPricing() { ? 'educational' : 'enterprise' - const priceInCentsProfessional = - groupPlans[usage].professional[currency][numberOfLicenses].price_in_cents - const priceInUnitProfessional = (priceInCentsProfessional / 100).toFixed() - const perUserPriceProfessional = parseFloat( - (priceInCentsProfessional / 100 / numberOfLicenses).toFixed(2) - ) + function calculatePrice(plan) { + const priceInCents = + groupPlans[usage][plan][currency][numberOfLicenses].price_in_cents + const priceInUnit = (priceInCents / 100).toFixed() + const perUserPrice = (priceInCents / 100 / numberOfLicenses).toFixed(2) + + return { priceInUnit, perUserPrice } + } + + const { + priceInUnit: priceInUnitProfessional, + perUserPrice: perUserPriceProfessional, + } = calculatePrice('professional') let displayPriceProfessional = `${currencySymbol}${priceInUnitProfessional}` let displayPerUserPriceProfessional = `${currencySymbol}${perUserPriceProfessional}` - const priceInCentsCollaborator = - groupPlans[usage].collaborator[currency][numberOfLicenses].price_in_cents - const priceInUnitCollaborator = (priceInCentsCollaborator / 100).toFixed() - const perUserPriceCollaborator = parseFloat( - (priceInCentsCollaborator / 100 / numberOfLicenses).toFixed(2) - ) + const { + priceInUnit: priceInUnitCollaborator, + perUserPrice: perUserPriceCollaborator, + } = calculatePrice('collaborator') let displayPriceCollaborator = `${currencySymbol}${priceInUnitCollaborator}` let displayPerUserPriceCollaborator = `${currencySymbol}${perUserPriceCollaborator}` @@ -68,47 +73,28 @@ export function updateMainGroupPlanPricing() { '[data-ol-plans-v2-group-price-per-user="professional"]' ).innerText = displayPerUserPriceProfessional - // educational discount can only be activated if numberOfLicenses is > 10 - if (numberOfLicenses < MINIMUM_NUMBER_OF_LICENSES_EDUCATIONAL_DISCOUNT) { - formEl.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ).disabled = true + // educational discount can only be activated if numberOfLicenses is >= 10 + const notEligibleForDiscount = + numberOfLicenses < MINIMUM_NUMBER_OF_LICENSES_EDUCATIONAL_DISCOUNT + + formEl + .querySelector( + '[data-ol-plans-v2-license-picker-educational-discount-label]' + ) + .classList.toggle('disabled', notEligibleForDiscount) + + formEl.querySelector( + '[data-ol-plans-v2-license-picker-educational-discount-input]' + ).disabled = notEligibleForDiscount + + if (notEligibleForDiscount) { + // force disable educational discount checkbox formEl.querySelector( '[data-ol-plans-v2-license-picker-educational-discount-input]' ).checked = false - formEl - .querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-label]' - ) - .classList.add('disabled') - } else { - formEl.querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-input]' - ).disabled = false - formEl - .querySelector( - '[data-ol-plans-v2-license-picker-educational-discount-label]' - ) - .classList.remove('disabled') } } -export function showGroupPlansLicensePicker() { - const el = document.querySelector( - '[data-ol-plans-v2-license-picker-container]' - ) - - el.hidden = false -} - -export function hideGroupPlansLicensePicker() { - const el = document.querySelector( - '[data-ol-plans-v2-license-picker-container]' - ) - - el.hidden = true -} - export function changeGroupPlanModalNumberOfLicenses() { const modalEl = document.querySelector('[data-ol-group-plan-modal]') const numberOfLicenses = document.querySelector( 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 index ba7673e2b8..a02efec199 100644 --- 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 @@ -1,226 +1,65 @@ // m-a stands for monthly-annual -// 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 = 'monthly' - -// only executed if switching to group tab -export function disableMonthlyAnnualSwitching() { +export function toggleMonthlyAnnualSwitching( + view, + currentMonthlyAnnualSwitchValue +) { const containerEl = document.querySelector( '[data-ol-plans-v2-m-a-switch-container]' ) const checkbox = containerEl.querySelector('input[type="checkbox"]') - containerEl.classList.add('disabled') + containerEl.classList.toggle('disabled', view === 'group') - checkbox.disabled = true - checkbox.checked = true + checkbox.disabled = view === 'group' + checkbox.checked = currentMonthlyAnnualSwitchValue === 'annual' + + 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-annual-selected', + currentMonthlyAnnualSwitchValue === 'annual' + ) - document - .querySelectorAll('[data-ol-plans-v2-table-container]') - .forEach(el => { - const period = el.getAttribute('data-ol-plans-v2-table-container') - if (period === 'annual') { - el.hidden = false - } else { - el.hidden = true - } - }) document .querySelectorAll('[data-ol-plans-v2-table-annual-price-before-discount]') .forEach(el => { - el.classList.remove('hidden') + el.classList.toggle( + 'hidden', + currentMonthlyAnnualSwitchValue === 'monthly' + ) }) -} -// executed if switching from group tab to individual and student tab -export function enableMonthlyAnnualSwitching() { - const containerEl = document.querySelector( - '[data-ol-plans-v2-m-a-switch-container]' - ) - const checkbox = containerEl.querySelector('input[type="checkbox"]') - containerEl.classList.remove('disabled') + document.querySelectorAll('[data-ol-plans-v2-period').forEach(el => { + const period = el.getAttribute('data-ol-plans-v2-period') - checkbox.disabled = false + el.hidden = currentMonthlyAnnualSwitchValue !== period + }) - if (currentMonthlyAnnualSwitchValue === 'annual') { - checkbox.checked = true - } else { - checkbox.checked = false - document - .querySelectorAll('[data-ol-plans-v2-table-container]') - .forEach(el => { - const period = el.getAttribute('data-ol-plans-v2-table-container') - if (period === 'annual') { - el.hidden = true - } else { - el.hidden = false - } - }) - document - .querySelectorAll('[data-ol-plans-v2-table-annual-price-before-discount]') - .forEach(el => { - el.classList.add('hidden') - }) - } -} - -export function hideMonthlyAnnualSwitchOnSmallScreen() { - const smallScreen = window.matchMedia('(max-width: 767px)').matches - - if (smallScreen) { - const el = document.querySelector('[data-ol-plans-v2-m-a-switch-container]') - - el.hidden = true - } -} - -export function showMonthlyAnnualSwitchOnSmallScreen() { - const smallScreen = window.matchMedia('(max-width: 767px)').matches - - if (smallScreen) { - const el = document.querySelector('[data-ol-plans-v2-m-a-switch-container]') - - el.hidden = false - } -} - -// in group tab, there are no "20% discount" -// tooltip in the monthly-annual switch "annual" text. -// so, we need to hide it -export function showMonthlyAnnualTooltip() { - const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - - el.hidden = false -} - -export function hideMonthlyAnnualTooltip() { - const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - - el.hidden = true -} - -// "20% discount" tooltip in the monthly-annual switch will have a different -// text and different colour, so we need to switch them accordingly -function switchMonthlyAnnualTooltip() { - const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - if (currentMonthlyAnnualSwitchValue === 'annual') { - el.classList.remove('plans-v2-m-a-tooltip-annual-selected') - document.querySelectorAll('[data-ol-tooltip-period]').forEach(childEl => { - const period = childEl.getAttribute('data-ol-tooltip-period') - if (period === 'monthly') { - childEl.hidden = false - } else { - childEl.hidden = true - } + 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 + ) }) - } else { - el.classList.add('plans-v2-m-a-tooltip-annual-selected') - document.querySelectorAll('[data-ol-tooltip-period]').forEach(childEl => { - const period = childEl.getAttribute('data-ol-tooltip-period') - if (period === 'annual') { - childEl.hidden = false - } else { - childEl.hidden = true - } - }) - } } function changeMonthlyAnnualTooltipPosition() { const smallScreen = window.matchMedia('(max-width: 767px)').matches const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]') - if (smallScreen) { - el.classList.replace('right', 'bottom') - } else { - el.classList.replace('bottom', 'right') - } -} - -// month and annual value will each have its own set of tables that we need to -// switch accordingly -function switchMonthlyAnnualTable() { - const isAnnualPricing = document.querySelector( - '[data-ol-plans-v2-m-a-switch] input[type="checkbox"]' - ).checked - - if (isAnnualPricing) { - currentMonthlyAnnualSwitchValue = 'annual' - } else { - currentMonthlyAnnualSwitchValue = 'monthly' - } - - document - .querySelectorAll('[data-ol-plans-v2-table-annual-price-before-discount]') - .forEach(el => { - if (isAnnualPricing) { - el.classList.remove('hidden') - } else { - el.classList.add('hidden') - } - }) - - document - .querySelectorAll('[data-ol-plans-v2-table-container]') - .forEach(el => { - const period = el.getAttribute('data-ol-plans-v2-table-container') - if (isAnnualPricing) { - if (period === 'annual') { - el.hidden = false - } else { - el.hidden = true - } - } else { - if (period === 'annual') { - el.hidden = true - } else { - el.hidden = false - } - } - }) -} - -export function underlineAnnualText() { - document - .querySelector('[data-ol-plans-v2-m-a-switch-monthly-text]') - .classList.remove('underline') - document - .querySelector('[data-ol-plans-v2-m-a-switch-annual-text]') - .classList.add('underline') -} - -function underlineMonthlyText() { - document - .querySelector('[data-ol-plans-v2-m-a-switch-monthly-text]') - .classList.add('underline') - document - .querySelector('[data-ol-plans-v2-m-a-switch-annual-text]') - .classList.remove('underline') -} - -// if annual is active, we need to underline the "annual" text -// if monthly is active, we need to underline the "monthly" text -export function switchUnderlineText() { - if (currentMonthlyAnnualSwitchValue === 'annual') { - underlineAnnualText() - } else { - underlineMonthlyText() - } + el.classList.toggle('bottom', smallScreen) + el.classList.toggle('right', !smallScreen) } // click event listener for monthly-annual switch export function setUpMonthlyAnnualSwitching() { - document - .querySelector('[data-ol-plans-v2-m-a-switch]') - .addEventListener('click', () => { - switchMonthlyAnnualTooltip() - switchMonthlyAnnualTable() - switchUnderlineText() - }) - 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 index b13a12564f..73357bcd77 100644 --- 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 @@ -1,61 +1,54 @@ import '../../../../marketing' import * as eventTracking from '../../../../infrastructure/event-tracking' +import { setUpStickyHeaderObserver } from './plans-v2-sticky-header' import { - setUpStickyHeaderObserver, - switchStickyHeader, -} from './plans-v2-sticky-header' -import { - disableMonthlyAnnualSwitching, - enableMonthlyAnnualSwitching, - hideMonthlyAnnualSwitchOnSmallScreen, - showMonthlyAnnualSwitchOnSmallScreen, - hideMonthlyAnnualTooltip, - showMonthlyAnnualTooltip, setUpMonthlyAnnualSwitching, - underlineAnnualText, - switchUnderlineText, + switchMonthlyAnnual, + toggleMonthlyAnnualSwitching, } from './plans-v2-m-a-switch' import { changeGroupPlanModalEducationalDiscount, changeGroupPlanModalNumberOfLicenses, - hideGroupPlansLicensePicker, - showGroupPlansLicensePicker, updateMainGroupPlanPricing, } from './plans-v2-group-plan' import { setUpGroupSubscriptionButtonAction } from './plans-v2-subscription-button' import { updateLinkTargets } from '../plans' +// 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 = 'monthly' + function selectTab(viewTab) { document.querySelectorAll('[data-ol-plans-v2-view-tab]').forEach(el => { - if (el.getAttribute('data-ol-plans-v2-view-tab') === viewTab) { - el.classList.add('active') - } else { - el.classList.remove('active') - } + el.classList.toggle( + 'active', + el.getAttribute('data-ol-plans-v2-view-tab') === viewTab + ) }) document.querySelectorAll('[data-ol-plans-v2-view]').forEach(el => { el.hidden = el.getAttribute('data-ol-plans-v2-view') !== viewTab }) - switchUnderlineText() - switchStickyHeader(viewTab) + document.querySelector('[data-ol-plans-v2-m-a-tooltip]').hidden = + viewTab === 'group' + document.querySelector('[data-ol-plans-v2-license-picker-container]').hidden = + viewTab !== 'group' + + document + .querySelector('[data-ol-plans-v2-m-a-switch-container]') + .setAttribute('data-ol-current-view', 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') { - disableMonthlyAnnualSwitching() - hideMonthlyAnnualTooltip() updateMainGroupPlanPricing() - underlineAnnualText() - showGroupPlansLicensePicker() - hideMonthlyAnnualSwitchOnSmallScreen() + toggleMonthlyAnnualSwitching(viewTab, 'annual') } else { - enableMonthlyAnnualSwitching() - showMonthlyAnnualTooltip() - hideGroupPlansLicensePicker() - showMonthlyAnnualSwitchOnSmallScreen() + toggleMonthlyAnnualSwitching(viewTab, currentMonthlyAnnualSwitchValue) } } @@ -97,6 +90,22 @@ function setUpGroupPlanPricingChange() { ) } +document + .querySelector('[data-ol-plans-v2-m-a-switch]') + .addEventListener('click', () => { + const isAnnualPricing = document.querySelector( + '[data-ol-plans-v2-m-a-switch] input[type="checkbox"]' + ).checked + + if (isAnnualPricing) { + currentMonthlyAnnualSwitchValue = 'annual' + } else { + currentMonthlyAnnualSwitchValue = 'monthly' + } + + switchMonthlyAnnual(currentMonthlyAnnualSwitchValue) + }) + setUpTabSwitching() setUpGroupPlanPricingChange() setUpMonthlyAnnualSwitching() 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 index d5173b52f6..47cd51605a 100644 --- 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 @@ -1,32 +1,9 @@ -// we have different sticky header according to plans (individual, group, and student) -// we need to show different sticky header based on active tab -// the value of attribute 'data-ol-plans-v2-table-sticky-header' can be individual, group, or student -export function switchStickyHeader(viewTab) { +function stickyHeaderObserverCallback(entry) { document .querySelectorAll('[data-ol-plans-v2-table-sticky-header]') - .forEach(el => { - const plan = el.getAttribute('data-ol-plans-v2-table-sticky-header') - - if (plan === viewTab) { - el.hidden = false - } else { - el.hidden = true - } - }) -} - -function stickyHeaderObserverCallback(entry) { - const entryItem = entry[0] - - if (entryItem.boundingClientRect.bottom <= 0) { - document - .querySelectorAll('[data-ol-plans-v2-table-sticky-header]') - .forEach(el => el.classList.remove('sticky')) - } else { - document - .querySelectorAll('[data-ol-plans-v2-table-sticky-header]') - .forEach(el => el.classList.add('sticky')) - } + .forEach(el => + el.classList.toggle('sticky', entry[0].boundingClientRect.bottom > 0) + ) } export function setUpStickyHeaderObserver() { 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 index 1d6fce2772..66ad71a74f 100644 --- 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 @@ -1,6 +1,10 @@ import { updateGroupModalPlanPricing } from '../../../../features/plans/group-plan-modal' -function changeGroupPlanModalRadioInputData(plan) { +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( @@ -9,12 +13,6 @@ function changeGroupPlanModalRadioInputData(plan) { groupModalRadioInputEl.checked = true updateGroupModalPlanPricing() -} - -function showGroupPlanModal(el) { - const plan = el.getAttribute('data-ol-start-new-subscription') - - changeGroupPlanModalRadioInputData(plan) const modalEl = $('[data-ol-group-plan-modal]') modalEl.modal() diff --git a/services/web/frontend/stylesheets/app/plans-v2.less b/services/web/frontend/stylesheets/app/plans-v2.less index b221dfd038..66a1dedca6 100644 --- a/services/web/frontend/stylesheets/app/plans-v2.less +++ b/services/web/frontend/stylesheets/app/plans-v2.less @@ -12,6 +12,14 @@ @plans-v2-table-sticky-header-z-index: 100; @plans-v2-table-border-radius: 20px; +.plans { + @media (max-width: @screen-xs-max) { + [data-ol-current-view='group'] [data-ol-plans-v2-m-a-switch-container] { + display: none; + } + } +} + .plans-v2-top-switch ul.plans-v2-nav { display: flex; justify-content: center; @@ -144,6 +152,10 @@ @media (max-width: @screen-xs-max) { margin-top: 25px; + + &[data-ol-current-view='group'] { + display: none; + } } }