Merge pull request #23488 from overleaf/ls-display-add-on-with-pending-cancellation

Display addOn that has pending cancellation

GitOrigin-RevId: 5fde493d1b706a1708e0cb4a2de6a7682fb1d1e0
This commit is contained in:
ilkin-overleaf
2025-02-11 11:45:17 +02:00
committed by Copybot
parent f142c2c0b4
commit 40f4b5bee7
3 changed files with 130 additions and 97 deletions
@@ -2005,6 +2005,7 @@
"youll_no_longer_need_to_remember_credentials": "",
"your_account_is_managed_by_admin_cant_join_additional_group": "",
"your_account_is_managed_by_your_group_admin": "",
"your_add_on_has_been_cancelled_and_will_remain_active_until_your_billing_cycle_ends_on": "",
"your_affiliation_is_confirmed": "",
"your_browser_does_not_support_this_feature": "",
"your_compile_timed_out": "",
@@ -18,7 +18,7 @@ import sparkle from '@/shared/svgs/sparkle.svg'
import { bsVersion } from '@/features/utils/bootstrap-5'
import classnames from 'classnames'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PendingRecurlyPlan } from '../../../../../../../../types/subscription/plan'
import { LICENSE_ADD_ON } from '@/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details'
type AddOnsProps = {
subscription: RecurlySubscription
@@ -26,114 +26,145 @@ type AddOnsProps = {
handleCancelClick: (code: string) => void
}
type AddOnProps = {
addOnCode: string
displayPrice: string | undefined
pendingCancellation: boolean
isAnnual: boolean
handleCancelClick: (code: string) => void
nextBillingDate: string
}
function resolveAddOnName(addOnCode: string) {
switch (addOnCode) {
case AI_ADD_ON_CODE:
case AI_STANDALONE_ANNUAL_PLAN_CODE:
case AI_STANDALONE_PLAN_CODE:
return ADD_ON_NAME
}
}
function AddOn({
addOnCode,
displayPrice,
pendingCancellation,
isAnnual,
handleCancelClick,
nextBillingDate,
}: AddOnProps) {
const { t } = useTranslation()
return (
<div className="add-on-card">
<div>
<img
alt="sparkle"
className="add-on-card-icon"
src={sparkle}
aria-hidden="true"
/>
</div>
<div className="add-on-card-content">
<div className="heading">{resolveAddOnName(addOnCode)}</div>
<div className="description small mt-1">
{pendingCancellation
? t(
'your_add_on_has_been_cancelled_and_will_remain_active_until_your_billing_cycle_ends_on',
{ nextBillingDate }
)
: isAnnual
? t('x_price_per_year', { price: displayPrice })
: t('x_price_per_month', { price: displayPrice })}
</div>
</div>
{!pendingCancellation && (
<div className="ms-auto">
<BootstrapVersionSwitcher
bs3={
<ControlledDropdown id="add-ons-actions" pullRight>
<BS3Dropdown.Toggle
noCaret
bsStyle={null}
className="add-on-options-toggle btn-secondary"
>
<MaterialIcon
type="more_vert"
accessibilityLabel={t('more_options')}
/>
</BS3Dropdown.Toggle>
<BS3Dropdown.Menu>
<BS3MenuItem onClick={() => handleCancelClick(addOnCode)}>
{t('cancel')}
</BS3MenuItem>
</BS3Dropdown.Menu>
</ControlledDropdown>
}
bs5={
<Dropdown align="end">
<DropdownToggle
id="add-on-dropdown-toggle"
className="add-on-options-toggle"
variant="secondary"
>
<MaterialIcon
type="more_vert"
accessibilityLabel={t('more_options')}
/>
</DropdownToggle>
<DropdownMenu flip={false}>
<OLDropdownMenuItem
onClick={() => handleCancelClick(addOnCode)}
as="button"
tabIndex={-1}
variant="danger"
>
{t('cancel')}
</OLDropdownMenuItem>
</DropdownMenu>
</Dropdown>
}
/>
</div>
)}
</div>
)
}
function AddOns({
subscription,
onStandalonePlan,
handleCancelClick,
}: AddOnsProps) {
const { t } = useTranslation()
const addOnsDisplayPrices =
subscription.recurly.addOnDisplayPricesWithoutAdditionalLicense
const addOnsDisplayPricesEntries = Object.entries(
onStandalonePlan
? {
[AI_STANDALONE_PLAN_CODE]: subscription.recurly.displayPrice,
}
: addOnsDisplayPrices
)
const pendingPlan = subscription.pendingPlan as PendingRecurlyPlan
const hasAiAddon = subscription.addOns?.some(
addOn => addOn.addOnCode === AI_ADD_ON_CODE
)
const pendingCancellation = Boolean(
hasAiAddon &&
pendingPlan &&
!pendingPlan.addOns?.some(addOn => addOn.add_on_code === AI_ADD_ON_CODE)
)
const resolveAddOnName = (addOnCode: string) => {
switch (addOnCode) {
case AI_ADD_ON_CODE:
case AI_STANDALONE_ANNUAL_PLAN_CODE:
case AI_STANDALONE_PLAN_CODE:
return ADD_ON_NAME
}
}
const addOnsDisplayPrices = onStandalonePlan
? {
[AI_STANDALONE_PLAN_CODE]: subscription.recurly.displayPrice,
}
: subscription.recurly.addOnDisplayPricesWithoutAdditionalLicense
const addOnsToDisplay = onStandalonePlan
? [{ addOnCode: AI_STANDALONE_PLAN_CODE }]
: subscription.addOns?.filter(addOn => addOn.addOnCode !== LICENSE_ADD_ON)
return (
<>
<h2 className={classnames('h3', bsVersion({ bs5: 'fw-bold' }))}>
{t('add_ons')}
</h2>
{addOnsDisplayPricesEntries.length > 0 ? (
addOnsDisplayPricesEntries.map(([code, displayPrice]) => (
<div className="add-on-card" key={code}>
<div>
<img
alt="sparkle"
className="add-on-card-icon"
src={sparkle}
aria-hidden="true"
/>
</div>
<div className="add-on-card-content">
<div className="heading">{resolveAddOnName(code)}</div>
<div className="description small mt-1">
{subscription.plan.annual
? t('x_price_per_year', { price: displayPrice })
: t('x_price_per_month', { price: displayPrice })}
</div>
</div>
{!pendingCancellation && (
<div className="ms-auto">
<BootstrapVersionSwitcher
bs3={
<ControlledDropdown id="add-ons-actions" pullRight>
<BS3Dropdown.Toggle
noCaret
bsStyle={null}
className="add-on-options-toggle btn-secondary"
>
<MaterialIcon
type="more_vert"
accessibilityLabel={t('more_options')}
/>
</BS3Dropdown.Toggle>
<BS3Dropdown.Menu>
<BS3MenuItem onClick={() => handleCancelClick(code)}>
{t('cancel')}
</BS3MenuItem>
</BS3Dropdown.Menu>
</ControlledDropdown>
}
bs5={
<Dropdown align="end">
<DropdownToggle
id="add-on-dropdown-toggle"
className="add-on-options-toggle"
variant="secondary"
>
<MaterialIcon
type="more_vert"
accessibilityLabel={t('more_options')}
/>
</DropdownToggle>
<DropdownMenu flip={false}>
<OLDropdownMenuItem
onClick={() => handleCancelClick(code)}
as="button"
tabIndex={-1}
variant="danger"
>
{t('cancel')}
</OLDropdownMenuItem>
</DropdownMenu>
</Dropdown>
}
/>
</div>
)}
</div>
{addOnsToDisplay && addOnsToDisplay.length > 0 ? (
addOnsToDisplay.map(addOn => (
<AddOn
addOnCode={addOn.addOnCode}
key={addOn.addOnCode}
isAnnual={Boolean(subscription.plan.annual)}
handleCancelClick={handleCancelClick}
pendingCancellation={
subscription.pendingPlan !== undefined &&
(subscription.pendingPlan.addOns ?? []).every(
pendingAddOn => pendingAddOn.addOnCode !== addOn.addOnCode
)
}
displayPrice={addOnsDisplayPrices[addOn.addOnCode]}
nextBillingDate={subscription.recurly.nextPaymentDueDate}
/>
))
) : (
<p>{t('you_dont_have_any_add_ons_on_your_account')}</p>
+1
View File
@@ -2567,6 +2567,7 @@
"your_account_is_managed_by_admin_cant_join_additional_group": "Your __appName__ account is managed by your current group admin (__admin__). This means you cant join additional group subscriptions. <0>Read more about Managed Users.</0>",
"your_account_is_managed_by_your_group_admin": "Your account is managed by your group admin. You cant change or delete your email address.",
"your_account_is_suspended": "Your account is suspended",
"your_add_on_has_been_cancelled_and_will_remain_active_until_your_billing_cycle_ends_on": "Your add-on has been cancelled and will remain active until your billing cycle ends on __nextBillingDate__",
"your_affiliation_is_confirmed": "Your <0>__institutionName__</0> affiliation is confirmed.",
"your_browser_does_not_support_this_feature": "Sorry, your browser doesnt support this feature. Please update your browser to its latest version.",
"your_compile_timed_out": "Your compile timed out",