Revert "fix: make subscription panel reactive to actual tier (#7354)"

This reverts commit d3d02d0d4b.
This commit is contained in:
bymyself
2025-12-10 23:54:22 -08:00
parent d3d02d0d4b
commit 4e68a266a0
6 changed files with 57 additions and 201 deletions

View File

@@ -1900,29 +1900,25 @@
},
"tiers": {
"founder": {
"name": "Founder's Edition",
"name": "Founder's Edition Standard",
"price": "20.00",
"benefits": {
"monthlyCredits": "5,460",
"monthlyCreditsLabel": "monthly credits",
"maxDuration": "30 min",
"maxDurationLabel": "max duration of each workflow run",
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
"addCreditsLabel": "Add more credits whenever",
"customLoRAsLabel": "Import your own LoRAs"
"monthlyCredits": "5,460 monthly credits",
"maxDuration": "30 min max duration of each workflow run",
"rtx6000": "RTX 6000 Pro (96GB VRAM)",
"addCredits": "Add more credits whenever"
}
},
"standard": {
"name": "Standard",
"price": "20.00",
"benefits": {
"monthlyCredits": "4,200",
"monthlyCreditsLabel": "monthly credits",
"maxDuration": "30 min",
"maxDurationLabel": "max duration of each workflow run",
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
"addCreditsLabel": "Add more credits whenever",
"customLoRAsLabel": "Import your own LoRAs"
"monthlyCredits": "4,200 monthly credits",
"maxDuration": "30 min max duration of each workflow run",
"rtx6000": "RTX 6000 Pro (96GB VRAM)",
"addCredits": "Add more credits whenever",
"customLoRAs": "Import your own LoRAs",
"videoEstimate": "164"
}
},
"creator": {
@@ -1940,15 +1936,14 @@
},
"pro": {
"name": "Pro",
"price": "100.00",
"price": "100.00",
"benefits": {
"monthlyCredits": "21,100",
"monthlyCreditsLabel": "monthly credits",
"maxDuration": "1 hr",
"maxDurationLabel": "max duration of each workflow run",
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
"addCreditsLabel": "Add more credits whenever",
"customLoRAsLabel": "Import your own LoRAs"
"monthlyCredits": "21,100 monthly credits",
"maxDuration": "1 hr max duration of each workflow run",
"rtx6000": "RTX 6000 Pro (96GB VRAM)",
"addCredits": "Add more credits whenever",
"customLoRAs": "Import your own LoRAs",
"videoEstimate": "821"
}
}
},

View File

@@ -353,21 +353,8 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu
import { useSubscriptionActions } from '@/platform/cloud/subscription/composables/useSubscriptionActions'
import { useSubscriptionCredits } from '@/platform/cloud/subscription/composables/useSubscriptionCredits'
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
import type { components } from '@/types/comfyRegistryTypes'
import { cn } from '@/utils/tailwindUtil'
type SubscriptionTier = components['schemas']['SubscriptionTier']
/** Maps API subscription tier values to i18n translation keys */
const TIER_TO_I18N_KEY: Record<SubscriptionTier, string> = {
STANDARD: 'standard',
CREATOR: 'creator',
PRO: 'pro',
FOUNDERS_EDITION: 'founder'
}
const DEFAULT_TIER_KEY = 'standard'
const { buildDocsUrl } = useExternalLink()
const { t } = useI18n()
@@ -376,20 +363,14 @@ const {
isCancelled,
formattedRenewalDate,
formattedEndDate,
subscriptionTier,
handleInvoiceHistory
} = useSubscription()
const { show: showSubscriptionDialog } = useSubscriptionDialog()
const tierKey = computed(() => {
const tier = subscriptionTier.value
if (!tier) return DEFAULT_TIER_KEY
return TIER_TO_I18N_KEY[tier] ?? DEFAULT_TIER_KEY
})
const tierName = computed(() => t(`subscription.tiers.${tierKey.value}.name`))
const tierPrice = computed(() => t(`subscription.tiers.${tierKey.value}.price`))
// Tier data - hardcoded for Creator tier as requested
const tierName = computed(() => t('subscription.tiers.creator.name'))
const tierPrice = computed(() => t('subscription.tiers.creator.price'))
// Tier benefits for v-for loop
type BenefitType = 'metric' | 'feature'
@@ -402,34 +383,33 @@ interface Benefit {
}
const tierBenefits = computed(() => {
const key = tierKey.value
const baseBenefits: Benefit[] = [
{
key: 'monthlyCredits',
type: 'metric',
value: t(`subscription.tiers.${key}.benefits.monthlyCredits`),
label: t(`subscription.tiers.${key}.benefits.monthlyCreditsLabel`)
value: t('subscription.tiers.creator.benefits.monthlyCredits'),
label: t('subscription.tiers.creator.benefits.monthlyCreditsLabel')
},
{
key: 'maxDuration',
type: 'metric',
value: t(`subscription.tiers.${key}.benefits.maxDuration`),
label: t(`subscription.tiers.${key}.benefits.maxDurationLabel`)
value: t('subscription.tiers.creator.benefits.maxDuration'),
label: t('subscription.tiers.creator.benefits.maxDurationLabel')
},
{
key: 'gpu',
type: 'feature',
label: t(`subscription.tiers.${key}.benefits.gpuLabel`)
label: t('subscription.tiers.creator.benefits.gpuLabel')
},
{
key: 'addCredits',
type: 'feature',
label: t(`subscription.tiers.${key}.benefits.addCreditsLabel`)
label: t('subscription.tiers.creator.benefits.addCreditsLabel')
},
{
key: 'customLoRAs',
type: 'feature',
label: t(`subscription.tiers.${key}.benefits.customLoRAsLabel`)
label: t('subscription.tiers.creator.benefits.customLoRAsLabel')
}
]

View File

@@ -139,7 +139,6 @@ import { computed, onBeforeUnmount, watch } from 'vue'
import CloudBadge from '@/components/topbar/CloudBadge.vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'
import StripePricingTable from '@/platform/cloud/subscription/components/StripePricingTable.vue'
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
import SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'
@@ -156,18 +155,8 @@ const emit = defineEmits<{
close: [subscribed: boolean]
}>()
const { fetchStatus, isActiveSubscription } = useSubscription()
// Legacy price for non-tier flow with locale-aware formatting
const formattedMonthlyPrice = new Intl.NumberFormat(
navigator.language || 'en-US',
{
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}
).format(MONTHLY_SUBSCRIPTION_PRICE)
const { formattedMonthlyPrice, fetchStatus, isActiveSubscription } =
useSubscription()
const { featureFlag } = useFeatureFlags()
const subscriptionTiersEnabled = featureFlag(
'subscription_tiers_enabled',

View File

@@ -5,6 +5,7 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { getComfyApiBaseUrl, getComfyPlatformBaseUrl } from '@/config/comfyApi'
import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'
import { t } from '@/i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
@@ -13,16 +14,18 @@ import {
useFirebaseAuthStore
} from '@/stores/firebaseAuthStore'
import { useDialogService } from '@/services/dialogService'
import type { operations } from '@/types/comfyRegistryTypes'
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'
type CloudSubscriptionCheckoutResponse = {
checkout_url: string
}
export type CloudSubscriptionStatusResponse = NonNullable<
operations['GetCloudSubscriptionStatus']['responses']['200']['content']['application/json']
>
export type CloudSubscriptionStatusResponse = {
is_active: boolean
subscription_id: string
renewal_date: string | null
end_date?: string | null
}
function useSubscriptionInternal() {
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
@@ -69,8 +72,8 @@ function useSubscriptionInternal() {
})
})
const subscriptionTier = computed(
() => subscriptionStatus.value?.subscription_tier ?? null
const formattedMonthlyPrice = computed(
() => `$${MONTHLY_SUBSCRIPTION_PRICE.toFixed(0)}`
)
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
@@ -224,7 +227,7 @@ function useSubscriptionInternal() {
isCancelled,
formattedRenewalDate,
formattedEndDate,
subscriptionTier,
formattedMonthlyPrice,
// Actions
subscribe,

View File

@@ -1,25 +1,18 @@
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue'
import { createI18n } from 'vue-i18n'
import SubscriptionPanel from '@/platform/cloud/subscription/components/SubscriptionPanel.vue'
// Mock state refs that can be modified between tests
const mockIsActiveSubscription = ref(false)
const mockIsCancelled = ref(false)
const mockSubscriptionTier = ref<
'STANDARD' | 'CREATOR' | 'PRO' | 'FOUNDERS_EDITION' | null
>('CREATOR')
// Mock composables - using computed to match composable return types
// Mock composables
const mockSubscriptionData = {
isActiveSubscription: computed(() => mockIsActiveSubscription.value),
isCancelled: computed(() => mockIsCancelled.value),
formattedRenewalDate: computed(() => '2024-12-31'),
formattedEndDate: computed(() => '2024-12-31'),
subscriptionTier: computed(() => mockSubscriptionTier.value),
isActiveSubscription: false,
isCancelled: false,
formattedRenewalDate: '2024-12-31',
formattedEndDate: '2024-12-31',
formattedMonthlyPrice: '$9.99',
manageSubscription: vi.fn(),
handleInvoiceHistory: vi.fn()
}
@@ -57,15 +50,6 @@ vi.mock(
})
)
vi.mock(
'@/platform/cloud/subscription/composables/useSubscriptionDialog',
() => ({
useSubscriptionDialog: () => ({
show: vi.fn()
})
})
)
// Create i18n instance for testing
const i18n = createI18n({
legacy: false,
@@ -74,15 +58,12 @@ const i18n = createI18n({
en: {
subscription: {
title: 'Subscription',
titleUnsubscribed: 'Subscribe',
perMonth: '/ month',
subscribeNow: 'Subscribe Now',
manageSubscription: 'Manage Subscription',
partnerNodesBalance: 'Partner Nodes Balance',
partnerNodesDescription: 'Credits for partner nodes',
totalCredits: 'Total Credits',
creditsRemainingThisMonth: 'Credits remaining this month',
creditsYouveAdded: "Credits you've added",
monthlyBonusDescription: 'Monthly bonus',
prepaidDescription: 'Prepaid credits',
monthlyCreditsRollover: 'Monthly credits rollover info',
@@ -90,67 +71,11 @@ const i18n = createI18n({
viewUsageHistory: 'View Usage History',
addCredits: 'Add Credits',
yourPlanIncludes: 'Your plan includes',
viewMoreDetailsPlans: 'View more details about plans & pricing',
learnMore: 'Learn More',
messageSupport: 'Message Support',
invoiceHistory: 'Invoice History',
partnerNodesCredits: 'Partner nodes pricing',
renewsDate: 'Renews {date}',
expiresDate: 'Expires {date}',
tiers: {
founder: {
name: "Founder's Edition",
price: '20.00',
benefits: {
monthlyCredits: '5,460',
monthlyCreditsLabel: 'monthly credits',
maxDuration: '30 min',
maxDurationLabel: 'max duration of each workflow run',
gpuLabel: 'RTX 6000 Pro (96GB VRAM)',
addCreditsLabel: 'Add more credits whenever',
customLoRAsLabel: 'Import your own LoRAs'
}
},
standard: {
name: 'Standard',
price: '20.00',
benefits: {
monthlyCredits: '4,200',
monthlyCreditsLabel: 'monthly credits',
maxDuration: '30 min',
maxDurationLabel: 'max duration of each workflow run',
gpuLabel: 'RTX 6000 Pro (96GB VRAM)',
addCreditsLabel: 'Add more credits whenever',
customLoRAsLabel: 'Import your own LoRAs'
}
},
creator: {
name: 'Creator',
price: '35.00',
benefits: {
monthlyCredits: '7,400',
monthlyCreditsLabel: 'monthly credits',
maxDuration: '30 min',
maxDurationLabel: 'max duration of each workflow run',
gpuLabel: 'RTX 6000 Pro (96GB VRAM)',
addCreditsLabel: 'Add more credits whenever',
customLoRAsLabel: 'Import your own LoRAs'
}
},
pro: {
name: 'Pro',
price: '100.00',
benefits: {
monthlyCredits: '21,100',
monthlyCreditsLabel: 'monthly credits',
maxDuration: '1 hr',
maxDurationLabel: 'max duration of each workflow run',
gpuLabel: 'RTX 6000 Pro (96GB VRAM)',
addCreditsLabel: 'Add more credits whenever',
customLoRAsLabel: 'Import your own LoRAs'
}
}
}
expiresDate: 'Expires {date}'
}
}
}
@@ -191,22 +116,18 @@ function createWrapper(overrides = {}) {
describe('SubscriptionPanel', () => {
beforeEach(() => {
vi.clearAllMocks()
// Reset mock state
mockIsActiveSubscription.value = false
mockIsCancelled.value = false
mockSubscriptionTier.value = 'CREATOR'
})
describe('subscription state functionality', () => {
it('shows correct UI for active subscription', () => {
mockIsActiveSubscription.value = true
mockSubscriptionData.isActiveSubscription = true
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Manage Subscription')
expect(wrapper.text()).toContain('Add Credits')
})
it('shows correct UI for inactive subscription', () => {
mockIsActiveSubscription.value = false
mockSubscriptionData.isActiveSubscription = false
const wrapper = createWrapper()
expect(wrapper.findComponent({ name: 'SubscribeButton' }).exists()).toBe(
true
@@ -216,32 +137,18 @@ describe('SubscriptionPanel', () => {
})
it('shows renewal date for active non-cancelled subscription', () => {
mockIsActiveSubscription.value = true
mockIsCancelled.value = false
mockSubscriptionData.isActiveSubscription = true
mockSubscriptionData.isCancelled = false
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Renews 2024-12-31')
})
it('shows expiry date for cancelled subscription', () => {
mockIsActiveSubscription.value = true
mockIsCancelled.value = true
mockSubscriptionData.isActiveSubscription = true
mockSubscriptionData.isCancelled = true
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Expires 2024-12-31')
})
it('displays FOUNDERS_EDITION tier correctly', () => {
mockSubscriptionTier.value = 'FOUNDERS_EDITION'
const wrapper = createWrapper()
expect(wrapper.text()).toContain("Founder's Edition")
expect(wrapper.text()).toContain('5,460')
})
it('displays CREATOR tier correctly', () => {
mockSubscriptionTier.value = 'CREATOR'
const wrapper = createWrapper()
expect(wrapper.text()).toContain('Creator')
expect(wrapper.text()).toContain('7,400')
})
})
describe('credit display functionality', () => {

View File

@@ -152,28 +152,10 @@ describe('useSubscription', () => {
expect(formattedRenewalDate.value).toBe('')
})
it('should return subscription tier from status', async () => {
vi.mocked(global.fetch).mockResolvedValue({
ok: true,
json: async () => ({
is_active: true,
subscription_id: 'sub_123',
subscription_tier: 'CREATOR',
renewal_date: '2025-11-16T12:00:00Z'
})
} as Response)
it('should format monthly price correctly', () => {
const { formattedMonthlyPrice } = useSubscription()
mockIsLoggedIn.value = true
const { subscriptionTier, fetchStatus } = useSubscription()
await fetchStatus()
expect(subscriptionTier.value).toBe('CREATOR')
})
it('should return null when subscription tier is not available', () => {
const { subscriptionTier } = useSubscription()
expect(subscriptionTier.value).toBeNull()
expect(formattedMonthlyPrice.value).toBe('$20')
})
})