Refactor new plans page (#8493)

* [web] hide the monthly/annual switch for small screens via css

Co-Authored-By: Jakob Ackermann <jakob.ackermann@overleaf.com>

* [web] merge logic for hiding elements shown in a subset of view

Co-Authored-By: M Fahru <m.fahru@overleaf.com>

* [web] hide the monthly/annual switch for small screens via css

Co-Authored-By: M Fahru <m.fahru@overleaf.com>

* [web] merge logic for hiding elements shown in a subset of view

Co-Authored-By: M Fahru <m.fahru@overleaf.com>

* fix inverted logic on monthly annual checking

* delete some duplicated logic and refactor

* merge switch functions

* move global variable into the main module

* simplify the enable and disable switch

* remove unused parseFloat

* simplify group plan pricing calculation

* simplify discount group plan logic

* simplify sticky header logic

* merge view and period switching

* fix underlining of switch text

* simplify class list toggling

* merging two function of the group plan

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 5e51690514bbf1dee2639011748c6a8470e1c19a
This commit is contained in:
M Fahru 2022-06-22 08:22:11 -04:00 committed by Copybot
parent 9e91685445
commit 361f6f245a
8 changed files with 146 additions and 314 deletions

View file

@ -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')

View file

@ -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

View file

@ -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(

View file

@ -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)
}

View file

@ -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()

View file

@ -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() {

View file

@ -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()

View file

@ -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;
}
}
}