mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
fix: make subscription panel reactive to actual tier (#7354)
Was previously hard-coded, now is actually reactive to value returned from server - Update CloudSubscriptionStatusResponse to use generated types from comfyRegistryTypes which includes subscription_tier - Add subscriptionTier computed to useSubscription composable - Make SubscriptionPanel tierName, tierPrice, and tierBenefits reactive to actual subscription tier from API - Normalize i18n tier structure with consistent value/label format - Add FOUNDERS_EDITION tier support ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7354-fix-make-subscription-panel-reactive-to-actual-tier-2c66d73d365081059a7be875c13fdd0c) by [Unito](https://www.unito.io) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
committed by
bymyself
parent
4b008eeaf7
commit
d3d02d0d4b
@@ -1900,25 +1900,29 @@
|
|||||||
},
|
},
|
||||||
"tiers": {
|
"tiers": {
|
||||||
"founder": {
|
"founder": {
|
||||||
"name": "Founder's Edition Standard",
|
"name": "Founder's Edition",
|
||||||
"price": "20.00",
|
"price": "20.00",
|
||||||
"benefits": {
|
"benefits": {
|
||||||
"monthlyCredits": "5,460 monthly credits",
|
"monthlyCredits": "5,460",
|
||||||
"maxDuration": "30 min max duration of each workflow run",
|
"monthlyCreditsLabel": "monthly credits",
|
||||||
"rtx6000": "RTX 6000 Pro (96GB VRAM)",
|
"maxDuration": "30 min",
|
||||||
"addCredits": "Add more credits whenever"
|
"maxDurationLabel": "max duration of each workflow run",
|
||||||
|
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||||
|
"addCreditsLabel": "Add more credits whenever",
|
||||||
|
"customLoRAsLabel": "Import your own LoRAs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"name": "Standard",
|
"name": "Standard",
|
||||||
"price": "20.00",
|
"price": "20.00",
|
||||||
"benefits": {
|
"benefits": {
|
||||||
"monthlyCredits": "4,200 monthly credits",
|
"monthlyCredits": "4,200",
|
||||||
"maxDuration": "30 min max duration of each workflow run",
|
"monthlyCreditsLabel": "monthly credits",
|
||||||
"rtx6000": "RTX 6000 Pro (96GB VRAM)",
|
"maxDuration": "30 min",
|
||||||
"addCredits": "Add more credits whenever",
|
"maxDurationLabel": "max duration of each workflow run",
|
||||||
"customLoRAs": "Import your own LoRAs",
|
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||||
"videoEstimate": "164"
|
"addCreditsLabel": "Add more credits whenever",
|
||||||
|
"customLoRAsLabel": "Import your own LoRAs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"creator": {
|
"creator": {
|
||||||
@@ -1936,14 +1940,15 @@
|
|||||||
},
|
},
|
||||||
"pro": {
|
"pro": {
|
||||||
"name": "Pro",
|
"name": "Pro",
|
||||||
"price": "100.00",
|
"price": "100.00",
|
||||||
"benefits": {
|
"benefits": {
|
||||||
"monthlyCredits": "21,100 monthly credits",
|
"monthlyCredits": "21,100",
|
||||||
"maxDuration": "1 hr max duration of each workflow run",
|
"monthlyCreditsLabel": "monthly credits",
|
||||||
"rtx6000": "RTX 6000 Pro (96GB VRAM)",
|
"maxDuration": "1 hr",
|
||||||
"addCredits": "Add more credits whenever",
|
"maxDurationLabel": "max duration of each workflow run",
|
||||||
"customLoRAs": "Import your own LoRAs",
|
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||||
"videoEstimate": "821"
|
"addCreditsLabel": "Add more credits whenever",
|
||||||
|
"customLoRAsLabel": "Import your own LoRAs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -353,8 +353,21 @@ import { useSubscription } from '@/platform/cloud/subscription/composables/useSu
|
|||||||
import { useSubscriptionActions } from '@/platform/cloud/subscription/composables/useSubscriptionActions'
|
import { useSubscriptionActions } from '@/platform/cloud/subscription/composables/useSubscriptionActions'
|
||||||
import { useSubscriptionCredits } from '@/platform/cloud/subscription/composables/useSubscriptionCredits'
|
import { useSubscriptionCredits } from '@/platform/cloud/subscription/composables/useSubscriptionCredits'
|
||||||
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||||
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import { cn } from '@/utils/tailwindUtil'
|
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 { buildDocsUrl } = useExternalLink()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -363,14 +376,20 @@ const {
|
|||||||
isCancelled,
|
isCancelled,
|
||||||
formattedRenewalDate,
|
formattedRenewalDate,
|
||||||
formattedEndDate,
|
formattedEndDate,
|
||||||
|
subscriptionTier,
|
||||||
handleInvoiceHistory
|
handleInvoiceHistory
|
||||||
} = useSubscription()
|
} = useSubscription()
|
||||||
|
|
||||||
const { show: showSubscriptionDialog } = useSubscriptionDialog()
|
const { show: showSubscriptionDialog } = useSubscriptionDialog()
|
||||||
|
|
||||||
// Tier data - hardcoded for Creator tier as requested
|
const tierKey = computed(() => {
|
||||||
const tierName = computed(() => t('subscription.tiers.creator.name'))
|
const tier = subscriptionTier.value
|
||||||
const tierPrice = computed(() => t('subscription.tiers.creator.price'))
|
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 benefits for v-for loop
|
// Tier benefits for v-for loop
|
||||||
type BenefitType = 'metric' | 'feature'
|
type BenefitType = 'metric' | 'feature'
|
||||||
@@ -383,33 +402,34 @@ interface Benefit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tierBenefits = computed(() => {
|
const tierBenefits = computed(() => {
|
||||||
|
const key = tierKey.value
|
||||||
const baseBenefits: Benefit[] = [
|
const baseBenefits: Benefit[] = [
|
||||||
{
|
{
|
||||||
key: 'monthlyCredits',
|
key: 'monthlyCredits',
|
||||||
type: 'metric',
|
type: 'metric',
|
||||||
value: t('subscription.tiers.creator.benefits.monthlyCredits'),
|
value: t(`subscription.tiers.${key}.benefits.monthlyCredits`),
|
||||||
label: t('subscription.tiers.creator.benefits.monthlyCreditsLabel')
|
label: t(`subscription.tiers.${key}.benefits.monthlyCreditsLabel`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'maxDuration',
|
key: 'maxDuration',
|
||||||
type: 'metric',
|
type: 'metric',
|
||||||
value: t('subscription.tiers.creator.benefits.maxDuration'),
|
value: t(`subscription.tiers.${key}.benefits.maxDuration`),
|
||||||
label: t('subscription.tiers.creator.benefits.maxDurationLabel')
|
label: t(`subscription.tiers.${key}.benefits.maxDurationLabel`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'gpu',
|
key: 'gpu',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
label: t('subscription.tiers.creator.benefits.gpuLabel')
|
label: t(`subscription.tiers.${key}.benefits.gpuLabel`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'addCredits',
|
key: 'addCredits',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
label: t('subscription.tiers.creator.benefits.addCreditsLabel')
|
label: t(`subscription.tiers.${key}.benefits.addCreditsLabel`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'customLoRAs',
|
key: 'customLoRAs',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
label: t('subscription.tiers.creator.benefits.customLoRAsLabel')
|
label: t(`subscription.tiers.${key}.benefits.customLoRAsLabel`)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ import { computed, onBeforeUnmount, watch } from 'vue'
|
|||||||
|
|
||||||
import CloudBadge from '@/components/topbar/CloudBadge.vue'
|
import CloudBadge from '@/components/topbar/CloudBadge.vue'
|
||||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||||
|
import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'
|
||||||
import StripePricingTable from '@/platform/cloud/subscription/components/StripePricingTable.vue'
|
import StripePricingTable from '@/platform/cloud/subscription/components/StripePricingTable.vue'
|
||||||
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
||||||
import SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'
|
import SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'
|
||||||
@@ -155,8 +156,18 @@ const emit = defineEmits<{
|
|||||||
close: [subscribed: boolean]
|
close: [subscribed: boolean]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formattedMonthlyPrice, fetchStatus, isActiveSubscription } =
|
const { fetchStatus, isActiveSubscription } = useSubscription()
|
||||||
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 { featureFlag } = useFeatureFlags()
|
const { featureFlag } = useFeatureFlags()
|
||||||
const subscriptionTiersEnabled = featureFlag(
|
const subscriptionTiersEnabled = featureFlag(
|
||||||
'subscription_tiers_enabled',
|
'subscription_tiers_enabled',
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
|||||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
import { getComfyApiBaseUrl, getComfyPlatformBaseUrl } from '@/config/comfyApi'
|
import { getComfyApiBaseUrl, getComfyPlatformBaseUrl } from '@/config/comfyApi'
|
||||||
import { MONTHLY_SUBSCRIPTION_PRICE } from '@/config/subscriptionPricesConfig'
|
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import { useTelemetry } from '@/platform/telemetry'
|
import { useTelemetry } from '@/platform/telemetry'
|
||||||
@@ -14,18 +13,16 @@ import {
|
|||||||
useFirebaseAuthStore
|
useFirebaseAuthStore
|
||||||
} from '@/stores/firebaseAuthStore'
|
} from '@/stores/firebaseAuthStore'
|
||||||
import { useDialogService } from '@/services/dialogService'
|
import { useDialogService } from '@/services/dialogService'
|
||||||
|
import type { operations } from '@/types/comfyRegistryTypes'
|
||||||
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'
|
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'
|
||||||
|
|
||||||
type CloudSubscriptionCheckoutResponse = {
|
type CloudSubscriptionCheckoutResponse = {
|
||||||
checkout_url: string
|
checkout_url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CloudSubscriptionStatusResponse = {
|
export type CloudSubscriptionStatusResponse = NonNullable<
|
||||||
is_active: boolean
|
operations['GetCloudSubscriptionStatus']['responses']['200']['content']['application/json']
|
||||||
subscription_id: string
|
>
|
||||||
renewal_date: string | null
|
|
||||||
end_date?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
function useSubscriptionInternal() {
|
function useSubscriptionInternal() {
|
||||||
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
|
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
|
||||||
@@ -72,8 +69,8 @@ function useSubscriptionInternal() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const formattedMonthlyPrice = computed(
|
const subscriptionTier = computed(
|
||||||
() => `$${MONTHLY_SUBSCRIPTION_PRICE.toFixed(0)}`
|
() => subscriptionStatus.value?.subscription_tier ?? null
|
||||||
)
|
)
|
||||||
|
|
||||||
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
|
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
|
||||||
@@ -227,7 +224,7 @@ function useSubscriptionInternal() {
|
|||||||
isCancelled,
|
isCancelled,
|
||||||
formattedRenewalDate,
|
formattedRenewalDate,
|
||||||
formattedEndDate,
|
formattedEndDate,
|
||||||
formattedMonthlyPrice,
|
subscriptionTier,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
subscribe,
|
subscribe,
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import { createTestingPinia } from '@pinia/testing'
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import SubscriptionPanel from '@/platform/cloud/subscription/components/SubscriptionPanel.vue'
|
import SubscriptionPanel from '@/platform/cloud/subscription/components/SubscriptionPanel.vue'
|
||||||
|
|
||||||
// Mock composables
|
// 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
|
||||||
const mockSubscriptionData = {
|
const mockSubscriptionData = {
|
||||||
isActiveSubscription: false,
|
isActiveSubscription: computed(() => mockIsActiveSubscription.value),
|
||||||
isCancelled: false,
|
isCancelled: computed(() => mockIsCancelled.value),
|
||||||
formattedRenewalDate: '2024-12-31',
|
formattedRenewalDate: computed(() => '2024-12-31'),
|
||||||
formattedEndDate: '2024-12-31',
|
formattedEndDate: computed(() => '2024-12-31'),
|
||||||
formattedMonthlyPrice: '$9.99',
|
subscriptionTier: computed(() => mockSubscriptionTier.value),
|
||||||
manageSubscription: vi.fn(),
|
|
||||||
handleInvoiceHistory: vi.fn()
|
handleInvoiceHistory: vi.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +57,15 @@ vi.mock(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vi.mock(
|
||||||
|
'@/platform/cloud/subscription/composables/useSubscriptionDialog',
|
||||||
|
() => ({
|
||||||
|
useSubscriptionDialog: () => ({
|
||||||
|
show: vi.fn()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// Create i18n instance for testing
|
// Create i18n instance for testing
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
@@ -58,12 +74,15 @@ const i18n = createI18n({
|
|||||||
en: {
|
en: {
|
||||||
subscription: {
|
subscription: {
|
||||||
title: 'Subscription',
|
title: 'Subscription',
|
||||||
|
titleUnsubscribed: 'Subscribe',
|
||||||
perMonth: '/ month',
|
perMonth: '/ month',
|
||||||
subscribeNow: 'Subscribe Now',
|
subscribeNow: 'Subscribe Now',
|
||||||
manageSubscription: 'Manage Subscription',
|
manageSubscription: 'Manage Subscription',
|
||||||
partnerNodesBalance: 'Partner Nodes Balance',
|
partnerNodesBalance: 'Partner Nodes Balance',
|
||||||
partnerNodesDescription: 'Credits for partner nodes',
|
partnerNodesDescription: 'Credits for partner nodes',
|
||||||
totalCredits: 'Total Credits',
|
totalCredits: 'Total Credits',
|
||||||
|
creditsRemainingThisMonth: 'Credits remaining this month',
|
||||||
|
creditsYouveAdded: "Credits you've added",
|
||||||
monthlyBonusDescription: 'Monthly bonus',
|
monthlyBonusDescription: 'Monthly bonus',
|
||||||
prepaidDescription: 'Prepaid credits',
|
prepaidDescription: 'Prepaid credits',
|
||||||
monthlyCreditsRollover: 'Monthly credits rollover info',
|
monthlyCreditsRollover: 'Monthly credits rollover info',
|
||||||
@@ -71,11 +90,67 @@ const i18n = createI18n({
|
|||||||
viewUsageHistory: 'View Usage History',
|
viewUsageHistory: 'View Usage History',
|
||||||
addCredits: 'Add Credits',
|
addCredits: 'Add Credits',
|
||||||
yourPlanIncludes: 'Your plan includes',
|
yourPlanIncludes: 'Your plan includes',
|
||||||
|
viewMoreDetailsPlans: 'View more details about plans & pricing',
|
||||||
learnMore: 'Learn More',
|
learnMore: 'Learn More',
|
||||||
messageSupport: 'Message Support',
|
messageSupport: 'Message Support',
|
||||||
invoiceHistory: 'Invoice History',
|
invoiceHistory: 'Invoice History',
|
||||||
|
partnerNodesCredits: 'Partner nodes pricing',
|
||||||
renewsDate: 'Renews {date}',
|
renewsDate: 'Renews {date}',
|
||||||
expiresDate: 'Expires {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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,18 +191,22 @@ function createWrapper(overrides = {}) {
|
|||||||
describe('SubscriptionPanel', () => {
|
describe('SubscriptionPanel', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
// Reset mock state
|
||||||
|
mockIsActiveSubscription.value = false
|
||||||
|
mockIsCancelled.value = false
|
||||||
|
mockSubscriptionTier.value = 'CREATOR'
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('subscription state functionality', () => {
|
describe('subscription state functionality', () => {
|
||||||
it('shows correct UI for active subscription', () => {
|
it('shows correct UI for active subscription', () => {
|
||||||
mockSubscriptionData.isActiveSubscription = true
|
mockIsActiveSubscription.value = true
|
||||||
const wrapper = createWrapper()
|
const wrapper = createWrapper()
|
||||||
expect(wrapper.text()).toContain('Manage Subscription')
|
expect(wrapper.text()).toContain('Manage Subscription')
|
||||||
expect(wrapper.text()).toContain('Add Credits')
|
expect(wrapper.text()).toContain('Add Credits')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows correct UI for inactive subscription', () => {
|
it('shows correct UI for inactive subscription', () => {
|
||||||
mockSubscriptionData.isActiveSubscription = false
|
mockIsActiveSubscription.value = false
|
||||||
const wrapper = createWrapper()
|
const wrapper = createWrapper()
|
||||||
expect(wrapper.findComponent({ name: 'SubscribeButton' }).exists()).toBe(
|
expect(wrapper.findComponent({ name: 'SubscribeButton' }).exists()).toBe(
|
||||||
true
|
true
|
||||||
@@ -137,18 +216,32 @@ describe('SubscriptionPanel', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('shows renewal date for active non-cancelled subscription', () => {
|
it('shows renewal date for active non-cancelled subscription', () => {
|
||||||
mockSubscriptionData.isActiveSubscription = true
|
mockIsActiveSubscription.value = true
|
||||||
mockSubscriptionData.isCancelled = false
|
mockIsCancelled.value = false
|
||||||
const wrapper = createWrapper()
|
const wrapper = createWrapper()
|
||||||
expect(wrapper.text()).toContain('Renews 2024-12-31')
|
expect(wrapper.text()).toContain('Renews 2024-12-31')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows expiry date for cancelled subscription', () => {
|
it('shows expiry date for cancelled subscription', () => {
|
||||||
mockSubscriptionData.isActiveSubscription = true
|
mockIsActiveSubscription.value = true
|
||||||
mockSubscriptionData.isCancelled = true
|
mockIsCancelled.value = true
|
||||||
const wrapper = createWrapper()
|
const wrapper = createWrapper()
|
||||||
expect(wrapper.text()).toContain('Expires 2024-12-31')
|
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', () => {
|
describe('credit display functionality', () => {
|
||||||
|
|||||||
@@ -152,10 +152,28 @@ describe('useSubscription', () => {
|
|||||||
expect(formattedRenewalDate.value).toBe('')
|
expect(formattedRenewalDate.value).toBe('')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should format monthly price correctly', () => {
|
it('should return subscription tier from status', async () => {
|
||||||
const { formattedMonthlyPrice } = useSubscription()
|
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)
|
||||||
|
|
||||||
expect(formattedMonthlyPrice.value).toBe('$20')
|
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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user