From 7209fe4b26bcdab40ddee68ff656ac4476dcd907 Mon Sep 17 00:00:00 2001 From: comfydesigner Date: Tue, 18 Aug 2026 11:14:50 -0700 Subject: [PATCH 01/16] feat(billing): render the Enterprise tier and hide its self-serve pricing surfaces --- src/locales/en/main.json | 3 ++ .../subscription/components/CreditsTile.vue | 5 +- .../composables/usePricingTableUrlLoader.ts | 14 +++++- .../subscription/constants/tierPricing.ts | 16 +++++- .../CurrentUserPopoverWorkspace.test.ts | 11 ++++ .../CurrentUserPopoverWorkspace.vue | 11 +++- .../SubscriptionPanelContentWorkspace.test.ts | 50 +++++++++++++++++++ .../SubscriptionPanelContentWorkspace.vue | 32 ++++++++++-- ...SubscriptionTransitionPreviewWorkspace.vue | 7 ++- .../composables/useWorkspaceTierLabel.test.ts | 14 +++++- .../composables/useWorkspaceTierLabel.ts | 3 +- 11 files changed, 153 insertions(+), 13 deletions(-) diff --git a/src/locales/en/main.json b/src/locales/en/main.json index fc856a020e3..9391e708f39 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -2864,6 +2864,9 @@ "free": { "name": "Free" }, + "enterprise": { + "name": "Enterprise" + }, "founder": { "name": "Founder's Edition" }, diff --git a/src/platform/cloud/subscription/components/CreditsTile.vue b/src/platform/cloud/subscription/components/CreditsTile.vue index fc4a3185024..e5368f2f65a 100644 --- a/src/platform/cloud/subscription/components/CreditsTile.vue +++ b/src/platform/cloud/subscription/components/CreditsTile.vue @@ -231,6 +231,7 @@ import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables import { useBillingPolicyCapabilities } from '@/platform/cloud/subscription/composables/useBillingPolicyCapabilities' import { DEFAULT_TIER_KEY, + isEnterpriseTier, toTierKey, getTierCredits } from '@/platform/cloud/subscription/constants/tierPricing' @@ -283,7 +284,9 @@ const tierKey = computed(() => { const creditPoolTotalCredits = computed(() => { const monthlyCredits = currentTeamCreditStop.value?.credits_monthly ?? - getTierCredits(tierKey.value) + (isEnterpriseTier(subscription.value?.tier) + ? null + : getTierCredits(tierKey.value)) if (monthlyCredits === null) return null return subscription.value?.duration === 'ANNUAL' ? monthlyCredits * 12 diff --git a/src/platform/cloud/subscription/composables/usePricingTableUrlLoader.ts b/src/platform/cloud/subscription/composables/usePricingTableUrlLoader.ts index 8a6447fc106..c8ad3f3d723 100644 --- a/src/platform/cloud/subscription/composables/usePricingTableUrlLoader.ts +++ b/src/platform/cloud/subscription/composables/usePricingTableUrlLoader.ts @@ -1,6 +1,10 @@ import { useRoute, useRouter } from 'vue-router' import { useBillingContext } from '@/composables/billing/useBillingContext' +import { + isEnterprisePlanSlug, + isEnterpriseTier +} from '@/platform/cloud/subscription/constants/tierPricing' import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' import { clearPreservedQuery, @@ -102,7 +106,7 @@ export function usePricingTableUrlLoader() { const route = useRoute() const router = useRouter() const subscriptionDialog = useSubscriptionDialog() - const { teamCreditStops, fetchPlans } = useBillingContext() + const { subscription, teamCreditStops, fetchPlans } = useBillingContext() const { permissions } = useWorkspaceUI() /** Reads `?pricing=`, strips it, and opens the table when the gate allows. */ @@ -139,6 +143,14 @@ export function usePricingTableUrlLoader() { if (typeof param !== 'string' || !param) return if (!permissions.value.canManageSubscription) return + // Enterprise is sales-managed: the pricing table never opens for it, even + // from a deep link. The param was already stripped above. + if ( + isEnterpriseTier(subscription.value?.tier) || + isEnterprisePlanSlug(subscription.value?.planSlug) + ) { + return + } const teamCheckoutRequest = getTeamCheckoutRequest( param, diff --git a/src/platform/cloud/subscription/constants/tierPricing.ts b/src/platform/cloud/subscription/constants/tierPricing.ts index 84c3aa088b0..e5cdd9ef553 100644 --- a/src/platform/cloud/subscription/constants/tierPricing.ts +++ b/src/platform/cloud/subscription/constants/tierPricing.ts @@ -58,7 +58,21 @@ export const DEFAULT_TIER_KEY: TierKey = 'standard' // TEAM is workspace-level, so it maps to no key in this personal plan catalog. export function toTierKey(tier: IngestSubscriptionTier): TierKey | null { - return tier === 'TEAM' ? null : TIER_TO_KEY[tier] + return tier === 'TEAM' ? null : (TIER_TO_KEY[tier] ?? null) +} + +// ENTERPRISE is a sales-managed workspace tier: absent from the personal +// catalog and (until the ingest enum ships it) from the generated types, so it +// is matched as a runtime string. It never self-serves plan changes. +const ENTERPRISE_TIER = 'ENTERPRISE' +const ENTERPRISE_PLAN_SLUG_PREFIX = 'enterprise' + +export function isEnterpriseTier(tier: string | null | undefined): boolean { + return tier?.toUpperCase() === ENTERPRISE_TIER +} + +export function isEnterprisePlanSlug(slug: string | null | undefined): boolean { + return slug?.toLowerCase().startsWith(ENTERPRISE_PLAN_SLUG_PREFIX) === true } // Includes the workspace-level TEAM, which toTierKey maps to null: a catalog diff --git a/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts b/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts index b9bbd285633..18c12620f33 100644 --- a/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts +++ b/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts @@ -367,6 +367,17 @@ describe('CurrentUserPopoverWorkspace', () => { expect(state.showPricingTable).toHaveBeenCalledOnce() }) + it('hides Plans & pricing for an enterprise workspace but keeps Manage plan', () => { + state.planSlug = 'enterprise_monthly' + state.canManageSubscription = true + renderComponent('team') + + expect( + screen.queryByTestId('plans-pricing-menu-item') + ).not.toBeInTheDocument() + expect(screen.getByTestId('manage-plan-menu-item')).toBeInTheDocument() + }) + for (const workspaceType of ['personal', 'team'] as const) { it(`opens workspace plan management for a ${workspaceType} owner`, async () => { const user = userEvent.setup() diff --git a/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue b/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue index 11e83adf04c..5ae42cdde25 100644 --- a/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue +++ b/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue @@ -229,6 +229,10 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useExternalLink } from '@/composables/useExternalLink' import { useBillingContext } from '@/composables/billing/useBillingContext' +import { + isEnterprisePlanSlug, + isEnterpriseTier +} from '@/platform/cloud/subscription/constants/tierPricing' import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue' import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' import { isCloud } from '@/platform/distribution/types' @@ -303,8 +307,13 @@ const displayedCredits = computed(() => { }) }) +const isEnterprisePlan = computed( + () => + isEnterpriseTier(subscription.value?.tier) || + isEnterprisePlanSlug(subscription.value?.planSlug) +) const showPlansAndPricing = computed( - () => permissions.value.canManageSubscription + () => permissions.value.canManageSubscription && !isEnterprisePlan.value ) const hasDelinquentSubscription = computed( () => diff --git a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts index 090aaf79c09..c9eaa564f45 100644 --- a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts +++ b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts @@ -391,6 +391,56 @@ describe('SubscriptionPanelContentWorkspace', () => { ) }) + it('renders Enterprise without price, benefits, or a plan-change action', () => { + mockHasTeamPlan.value = false + mockPlanSlug.value = 'enterprise_monthly' + mockCurrentTeamCreditStop.value = null + renderComponent() + + expect(screen.getByText('Enterprise')).toBeInTheDocument() + expect(screen.queryByText('$665')).not.toBeInTheDocument() + expect(screen.queryByText('USD / mo')).not.toBeInTheDocument() + expect( + screen.queryByRole('button', { name: /change plan|upgrade plan/i }) + ).not.toBeInTheDocument() + expect( + screen.getByText(`Renews on ${formatPanelDate(RENEWAL_DATE_ISO)}`) + ).toBeInTheDocument() + }) + + it('labels a scheduled change to Enterprise', () => { + const basePlans = mockPlans.value + mockScheduledPlanSlug.value = 'enterprise_monthly' + mockChangeAt.value = END_DATE_ISO + mockPlans.value = [ + ...basePlans, + { + slug: 'enterprise_monthly', + tier: 'PRO', + duration: 'MONTHLY', + price_cents: 0, + credits_cents: 0, + max_seats: 1, + availability: { available: true }, + seat_summary: { + seat_count: 1, + total_cost_cents: 0, + total_credits_cents: 0 + } + } + ] + try { + renderComponent() + expect( + screen.getByText( + `Changes to Enterprise on ${formatPanelDate(END_DATE_ISO)}` + ) + ).toBeInTheDocument() + } finally { + mockPlans.value = basePlans + } + }) + it('shows a scheduled plan change instead of the renewal date', () => { mockScheduledPlanSlug.value = 'pro-annual' mockChangeAt.value = END_DATE_ISO diff --git a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue index d4560f92180..c8055ef23d2 100644 --- a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue +++ b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue @@ -225,7 +225,10 @@ severity="warn" /> -
+
{{ displayPrice }} {{ priceUnitLabel }}
@@ -276,7 +279,8 @@ v-else-if=" !isSubscriptionCancelled && canAccessSubscriptionFeatures && - permissions.canManageSubscription + permissions.canManageSubscription && + !isEnterprisePlan " size="lg" variant="secondary" @@ -417,6 +421,10 @@ import { useBillingContext } from '@/composables/billing/useBillingContext' import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog' import { useFreeTierQuota } from '@/platform/cloud/subscription/composables/useFreeTierQuota' import type { TierBenefit } from '@/platform/cloud/subscription/utils/tierBenefits' +import { + isEnterprisePlanSlug, + isEnterpriseTier +} from '@/platform/cloud/subscription/constants/tierPricing' import { getCommonTierBenefits } from '@/platform/cloud/subscription/utils/tierBenefits' import { isCloud } from '@/platform/distribution/types' import { useResubscribe } from '@/platform/workspace/composables/useResubscribe' @@ -566,6 +574,12 @@ const scheduledPlanName = computed(() => { (plan) => plan.slug === subscription.value?.scheduledPlanSlug ) if (!scheduledPlan) return '' + if ( + isEnterprisePlanSlug(scheduledPlan.slug) || + isEnterpriseTier(scheduledPlan.tier) + ) { + return t('subscription.tiers.enterprise.name') + } if (scheduledPlan.slug.startsWith('team')) { return t('subscription.teamPlanName') } @@ -627,10 +641,19 @@ const subscriptionTierName = computed(() => { : baseName }) -const planDisplayName = computed(() => - isTeamPlan.value ? t('subscription.teamPlanName') : subscriptionTierName.value +const isEnterprisePlan = computed( + () => + isEnterpriseTier(subscription.value?.tier) || + isEnterprisePlanSlug(subscription.value?.planSlug) ) +const planDisplayName = computed(() => { + if (isEnterprisePlan.value) return t('subscription.tiers.enterprise.name') + return isTeamPlan.value + ? t('subscription.teamPlanName') + : subscriptionTierName.value +}) + const tierKey = computed(() => resolveSubscriptionTierKey(subscription.value?.tier) ) @@ -643,6 +666,7 @@ const TEAM_PERK_KEYS = [ ] as const const tierBenefits = computed((): TierBenefit[] => { + if (isEnterprisePlan.value) return [] if (isTeamActive.value || showInactiveTeamSubscription.value) { return TEAM_PERK_KEYS.map((key) => ({ key, diff --git a/src/platform/workspace/components/SubscriptionTransitionPreviewWorkspace.vue b/src/platform/workspace/components/SubscriptionTransitionPreviewWorkspace.vue index 6140c992fb1..4d007962d47 100644 --- a/src/platform/workspace/components/SubscriptionTransitionPreviewWorkspace.vue +++ b/src/platform/workspace/components/SubscriptionTransitionPreviewWorkspace.vue @@ -257,7 +257,7 @@ defineEmits<{ back: [] }>() -const { t, n } = useI18n() +const { t, te, n } = useI18n() const { subscription } = useBillingContext() function openVerification() { @@ -266,7 +266,10 @@ function openVerification() { } function formatTierName(tier: string): string { - return t(`subscription.tiers.${tier.toLowerCase()}.name`) + const nameKey = `subscription.tiers.${tier.toLowerCase()}.name` + if (te(nameKey)) return t(nameKey) + const lower = tier.toLowerCase() + return lower.charAt(0).toUpperCase() + lower.slice(1) } function isTeamTier(tier: string): boolean { diff --git a/src/platform/workspace/composables/useWorkspaceTierLabel.test.ts b/src/platform/workspace/composables/useWorkspaceTierLabel.test.ts index 1e8f69d872a..a6d799510d5 100644 --- a/src/platform/workspace/composables/useWorkspaceTierLabel.test.ts +++ b/src/platform/workspace/composables/useWorkspaceTierLabel.test.ts @@ -12,7 +12,8 @@ vi.mock('vue-i18n', () => ({ 'subscription.tiers.standard.name': 'Standard', 'subscription.tiers.creator.name': 'Creator', 'subscription.tiers.pro.name': 'Pro', - 'subscription.tiers.founder.name': "Founder's Edition" + 'subscription.tiers.founder.name': "Founder's Edition", + 'subscription.tiers.enterprise.name': 'Enterprise' } return tierNames[key] ?? key }) @@ -103,12 +104,21 @@ describe('useWorkspaceTierLabel', () => { expect(result).toBeNull() }) - it('returns null when plan slug does not match any known tier', () => { + it('labels enterprise plan slugs', () => { const result = getTierLabel({ isSubscribed: true, subscriptionPlan: 'ENTERPRISE_CUSTOM', subscriptionTier: null }) + expect(result).toBe('Enterprise') + }) + + it('returns null when plan slug does not match any known tier', () => { + const result = getTierLabel({ + isSubscribed: true, + subscriptionPlan: 'ULTRA_CUSTOM', + subscriptionTier: null + }) expect(result).toBeNull() }) diff --git a/src/platform/workspace/composables/useWorkspaceTierLabel.ts b/src/platform/workspace/composables/useWorkspaceTierLabel.ts index 2956c8c0c4d..4e17239b724 100644 --- a/src/platform/workspace/composables/useWorkspaceTierLabel.ts +++ b/src/platform/workspace/composables/useWorkspaceTierLabel.ts @@ -8,7 +8,8 @@ const tierKeyMap: Record = { CREATOR: 'creator', PRO: 'pro', FOUNDER: 'founder', - FOUNDERS_EDITION: 'founder' + FOUNDERS_EDITION: 'founder', + ENTERPRISE: 'enterprise' } interface WorkspaceSubscriptionInfo { From 5ee9b282f027de093ee231f6a08cae4ff7562980 Mon Sep 17 00:00:00 2001 From: comfydesigner Date: Tue, 18 Aug 2026 11:41:53 -0700 Subject: [PATCH 02/16] fix(billing): stop the billing policy state crashing on runtime-only tiers --- .../composables/useBillingPolicyState.test.ts | 38 +++++++++++++++++++ .../composables/useBillingPolicyState.ts | 13 ++++++- .../SubscriptionPanelContentWorkspace.vue | 7 ++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/platform/cloud/subscription/composables/useBillingPolicyState.test.ts b/src/platform/cloud/subscription/composables/useBillingPolicyState.test.ts index 64bcf2fa3cf..eae54fe8f0e 100644 --- a/src/platform/cloud/subscription/composables/useBillingPolicyState.test.ts +++ b/src/platform/cloud/subscription/composables/useBillingPolicyState.test.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from 'vitest' +import type { IngestSubscriptionTier } from '@/platform/cloud/subscription/constants/tierPricing' + import { deriveBillingPolicyState } from './useBillingPolicyState' +const runtimeTier = (tier: string) => tier as unknown as IngestSubscriptionTier + describe('deriveBillingPolicyState', () => { it.for<[string, boolean]>([ ['LocalWithoutActiveSubscription', false], @@ -64,6 +68,40 @@ describe('deriveBillingPolicyState', () => { } ) + it.for<[string, boolean]>([ + ['LocalAndTeam', false], + ['CloudAndTeam', true] + ])( + 'maps an active Enterprise plan to the team policy state %s (isCloud=%s)', + ([kind, isCloud]) => { + expect( + deriveBillingPolicyState({ + isCloud, + canAccessSubscriptionFeatures: true, + isTeamPlan: false, + tier: runtimeTier('ENTERPRISE') + }) + ).toEqual({ kind }) + } + ) + + it.for<[string, boolean]>([ + ['LocalAndUnknown', false], + ['CloudAndUnknown', true] + ])( + 'degrades a tier outside the generated enum to %s instead of crashing (isCloud=%s)', + ([kind, isCloud]) => { + expect( + deriveBillingPolicyState({ + isCloud, + canAccessSubscriptionFeatures: true, + isTeamPlan: false, + tier: runtimeTier('ULTRA') + }) + ).toEqual({ kind }) + } + ) + it.for<[string, boolean]>([ ['LocalAndTeam', false], ['CloudAndTeam', true] diff --git a/src/platform/cloud/subscription/composables/useBillingPolicyState.ts b/src/platform/cloud/subscription/composables/useBillingPolicyState.ts index fe0df52e7df..371b4e51787 100644 --- a/src/platform/cloud/subscription/composables/useBillingPolicyState.ts +++ b/src/platform/cloud/subscription/composables/useBillingPolicyState.ts @@ -2,6 +2,7 @@ import { computed } from 'vue' import { useBillingContext } from '@/composables/billing/useBillingContext' import type { IngestSubscriptionTier } from '@/platform/cloud/subscription/constants/tierPricing' +import { isEnterpriseTier } from '@/platform/cloud/subscription/constants/tierPricing' import { isCloud } from '@/platform/distribution/types' import type { BillingPolicyState } from '../billingPolicyState' @@ -30,6 +31,12 @@ export function deriveBillingPolicyState(input: { return { kind: `${distribution}WithoutActiveSubscription` } } + // ENTERPRISE arrives as a runtime string before the generated enum carries + // it; it is a workspace-level plan, so it takes the team policy state. + if (isEnterpriseTier(input.tier)) { + return { kind: `${distribution}AndTeam` } + } + switch (input.tier) { case 'FREE': return { kind: `${distribution}AndFree` } @@ -45,8 +52,10 @@ export function deriveBillingPolicyState(input: { return { kind: `${distribution}AndTeam` } case null: return { kind: `${distribution}AndUnknown` } - default: - return input.tier satisfies never + default: { + input.tier satisfies never + return { kind: `${distribution}AndUnknown` } + } } } diff --git a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue index c8055ef23d2..61fcb70ec20 100644 --- a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue +++ b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue @@ -324,9 +324,10 @@
From ca29a67851f1a0e9a3586c79c5b8ee5fbf74ff72 Mon Sep 17 00:00:00 2001 From: comfydesigner Date: Tue, 18 Aug 2026 12:13:43 -0700 Subject: [PATCH 03/16] =?UTF-8?q?fix(billing):=20close=20enterprise=20gaps?= =?UTF-8?q?=20from=20review=20=E2=80=94=20slug-only=20credits=20fallback,?= =?UTF-8?q?=20reactivation=20gates,=20slug=20casing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cloud/subscription/components/CreditsTile.vue | 4 +++- .../components/CurrentUserPopoverWorkspace.test.ts | 12 ++++++++++++ .../components/CurrentUserPopoverWorkspace.vue | 9 +++++---- .../SubscriptionPanelContentWorkspace.test.ts | 12 ++++++++++++ .../components/SubscriptionPanelContentWorkspace.vue | 3 ++- .../composables/useWorkspaceTierLabel.test.ts | 9 +++++++++ .../workspace/composables/useWorkspaceTierLabel.ts | 2 +- 7 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/platform/cloud/subscription/components/CreditsTile.vue b/src/platform/cloud/subscription/components/CreditsTile.vue index e5368f2f65a..63d8ea23c4d 100644 --- a/src/platform/cloud/subscription/components/CreditsTile.vue +++ b/src/platform/cloud/subscription/components/CreditsTile.vue @@ -231,6 +231,7 @@ import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables import { useBillingPolicyCapabilities } from '@/platform/cloud/subscription/composables/useBillingPolicyCapabilities' import { DEFAULT_TIER_KEY, + isEnterprisePlanSlug, isEnterpriseTier, toTierKey, getTierCredits @@ -284,7 +285,8 @@ const tierKey = computed(() => { const creditPoolTotalCredits = computed(() => { const monthlyCredits = currentTeamCreditStop.value?.credits_monthly ?? - (isEnterpriseTier(subscription.value?.tier) + (isEnterpriseTier(subscription.value?.tier) || + isEnterprisePlanSlug(subscription.value?.planSlug) ? null : getTierCredits(tierKey.value)) if (monthlyCredits === null) return null diff --git a/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts b/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts index 18c12620f33..a37b1a8209c 100644 --- a/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts +++ b/src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts @@ -367,6 +367,18 @@ describe('CurrentUserPopoverWorkspace', () => { expect(state.showPricingTable).toHaveBeenCalledOnce() }) + it('hides Resubscribe for a cancelled enterprise workspace', () => { + state.planSlug = 'enterprise_monthly' + state.isCancelled = true + state.canManageSubscription = true + state.canManageSubscriptionLifecycle = true + renderComponent('team') + + expect( + screen.queryByRole('button', { name: 'Resubscribe' }) + ).not.toBeInTheDocument() + }) + it('hides Plans & pricing for an enterprise workspace but keeps Manage plan', () => { state.planSlug = 'enterprise_monthly' state.canManageSubscription = true diff --git a/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue b/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue index 5ae42cdde25..24df1117047 100644 --- a/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue +++ b/src/platform/workspace/components/CurrentUserPopoverWorkspace.vue @@ -328,10 +328,11 @@ const showManagePlan = computed( ) const showSubscribeAction = computed( () => - (isCancelled.value && permissions.value.canManageSubscriptionLifecycle) || - (!canAccessSubscriptionFeatures.value && - !hasDelinquentSubscription.value && - permissions.value.canManageSubscription) + !isEnterprisePlan.value && + ((isCancelled.value && permissions.value.canManageSubscriptionLifecycle) || + (!canAccessSubscriptionFeatures.value && + !hasDelinquentSubscription.value && + permissions.value.canManageSubscription)) ) const handleOpenUserSettings = () => { diff --git a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts index c9eaa564f45..0e9a62c1c64 100644 --- a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts +++ b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.test.ts @@ -408,6 +408,18 @@ describe('SubscriptionPanelContentWorkspace', () => { ).toBeInTheDocument() }) + it('hides Reactivate for a cancelled enterprise plan', () => { + mockHasTeamPlan.value = false + mockPlanSlug.value = 'enterprise_monthly' + mockCurrentTeamCreditStop.value = null + mockSubscriptionStatus.value = 'canceled' + renderComponent() + + expect( + screen.queryByRole('button', { name: /reactivate/i }) + ).not.toBeInTheDocument() + }) + it('labels a scheduled change to Enterprise', () => { const basePlans = mockPlans.value mockScheduledPlanSlug.value = 'enterprise_monthly' diff --git a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue index 61fcb70ec20..24be58a46e9 100644 --- a/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue +++ b/src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue @@ -265,7 +265,8 @@