Przeglądaj źródła

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

Display addOn that has pending cancellation

GitOrigin-RevId: 5fde493d1b706a1708e0cb4a2de6a7682fb1d1e0
ilkin-overleaf 1 rok temu
rodzic
commit
61ddc73d87

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -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": "",

+ 128 - 97
services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx

@@ -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 - 0
services/web/locales/en.json

@@ -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 can’t 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 can’t 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 doesn’t support this feature. Please update your browser to its latest version.",
   "your_compile_timed_out": "Your compile timed out",