mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
[backport cloud/1.35] Fix(cloud)/pricing annual misc (#7704)
Backport of #7701 to `cloud/1.35` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7704-backport-cloud-1-35-Fix-cloud-pricing-annual-misc-2d16d73d365081868447db732608a2c7) by [Unito](https://www.unito.io) Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com> Co-authored-by: bymyself <cbyrne@comfy.org>
This commit is contained in:
@@ -1921,6 +1921,7 @@
|
||||
"billedYearly": "{total} Billed yearly",
|
||||
"monthly": "Monthly",
|
||||
"yearly": "Yearly",
|
||||
"tierNameYearly": "{name} Yearly",
|
||||
"messageSupport": "Message support",
|
||||
"invoiceHistory": "Invoice history",
|
||||
"benefits": {
|
||||
|
||||
@@ -331,8 +331,9 @@ const tiers: PricingTierConfig[] = [
|
||||
|
||||
const { n } = useI18n()
|
||||
const { getAuthHeader } = useFirebaseAuthStore()
|
||||
const { isActiveSubscription, subscriptionTier } = useSubscription()
|
||||
const { accessBillingPortal, reportError } = useFirebaseAuthActions()
|
||||
const { isActiveSubscription, subscriptionTier, isYearlySubscription } =
|
||||
useSubscription()
|
||||
const { reportError } = useFirebaseAuthActions()
|
||||
const { wrapWithErrorHandlingAsync } = useErrorHandling()
|
||||
|
||||
const isLoading = ref(false)
|
||||
@@ -344,8 +345,16 @@ const currentTierKey = computed<TierKey | null>(() =>
|
||||
subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null
|
||||
)
|
||||
|
||||
const isCurrentPlan = (tierKey: CheckoutTierKey): boolean =>
|
||||
currentTierKey.value === tierKey
|
||||
const isCurrentPlan = (tierKey: CheckoutTierKey): boolean => {
|
||||
if (!currentTierKey.value) return false
|
||||
|
||||
const selectedIsYearly = currentBillingCycle.value === 'yearly'
|
||||
|
||||
return (
|
||||
currentTierKey.value === tierKey &&
|
||||
isYearlySubscription.value === selectedIsYearly
|
||||
)
|
||||
}
|
||||
|
||||
const togglePopover = (event: Event) => {
|
||||
popover.value.toggle(event)
|
||||
@@ -353,9 +362,15 @@ const togglePopover = (event: Event) => {
|
||||
|
||||
const getButtonLabel = (tier: PricingTierConfig): string => {
|
||||
if (isCurrentPlan(tier.key)) return t('subscription.currentPlan')
|
||||
if (!isActiveSubscription.value)
|
||||
return t('subscription.subscribeTo', { plan: tier.name })
|
||||
return t('subscription.changeTo', { plan: tier.name })
|
||||
|
||||
const planName =
|
||||
currentBillingCycle.value === 'yearly'
|
||||
? t('subscription.tierNameYearly', { name: tier.name })
|
||||
: tier.name
|
||||
|
||||
return isActiveSubscription.value
|
||||
? t('subscription.changeTo', { plan: planName })
|
||||
: t('subscription.subscribeTo', { plan: planName })
|
||||
}
|
||||
|
||||
const getButtonSeverity = (tier: PricingTierConfig): 'primary' | 'secondary' =>
|
||||
@@ -428,13 +443,9 @@ const handleSubscribe = wrapWithErrorHandlingAsync(
|
||||
loadingTier.value = tierKey
|
||||
|
||||
try {
|
||||
if (isActiveSubscription.value) {
|
||||
await accessBillingPortal()
|
||||
} else {
|
||||
const response = await initiateCheckout(tierKey)
|
||||
if (response.checkout_url) {
|
||||
window.open(response.checkout_url, '_blank')
|
||||
}
|
||||
const response = await initiateCheckout(tierKey)
|
||||
if (response.checkout_url) {
|
||||
window.open(response.checkout_url, '_blank')
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
|
||||
@@ -365,9 +365,9 @@ import { useSubscriptionCredits } from '@/platform/cloud/subscription/composable
|
||||
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||
import {
|
||||
DEFAULT_TIER_KEY,
|
||||
TIER_FEATURES,
|
||||
TIER_TO_KEY,
|
||||
getTierCredits,
|
||||
getTierFeatures,
|
||||
getTierPrice
|
||||
} from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
@@ -383,6 +383,7 @@ const {
|
||||
formattedEndDate,
|
||||
subscriptionTier,
|
||||
subscriptionTierName,
|
||||
isYearlySubscription,
|
||||
handleInvoiceHistory
|
||||
} = useSubscription()
|
||||
|
||||
@@ -393,7 +394,9 @@ const tierKey = computed(() => {
|
||||
if (!tier) return DEFAULT_TIER_KEY
|
||||
return TIER_TO_KEY[tier] ?? DEFAULT_TIER_KEY
|
||||
})
|
||||
const tierPrice = computed(() => getTierPrice(tierKey.value))
|
||||
const tierPrice = computed(() =>
|
||||
getTierPrice(tierKey.value, isYearlySubscription.value)
|
||||
)
|
||||
|
||||
// Tier benefits for v-for loop
|
||||
type BenefitType = 'metric' | 'feature'
|
||||
@@ -433,7 +436,7 @@ const tierBenefits = computed((): Benefit[] => {
|
||||
}
|
||||
]
|
||||
|
||||
if (TIER_FEATURES[key].customLoRAs) {
|
||||
if (getTierFeatures(key).customLoRAs) {
|
||||
benefits.push({
|
||||
key: 'customLoRAs',
|
||||
type: 'feature',
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
useFirebaseAuthStore
|
||||
} from '@/stores/firebaseAuthStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import type { components, operations } from '@/types/comfyRegistryTypes'
|
||||
import { TIER_TO_KEY } from '@/platform/cloud/subscription/constants/tierPricing'
|
||||
import type { operations } from '@/types/comfyRegistryTypes'
|
||||
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'
|
||||
|
||||
type CloudSubscriptionCheckoutResponse = NonNullable<
|
||||
@@ -24,15 +25,6 @@ export type CloudSubscriptionStatusResponse = NonNullable<
|
||||
operations['GetCloudSubscriptionStatus']['responses']['200']['content']['application/json']
|
||||
>
|
||||
|
||||
type SubscriptionTier = components['schemas']['SubscriptionTier']
|
||||
|
||||
const TIER_TO_I18N_KEY: Record<SubscriptionTier, string> = {
|
||||
STANDARD: 'standard',
|
||||
CREATOR: 'creator',
|
||||
PRO: 'pro',
|
||||
FOUNDERS_EDITION: 'founder'
|
||||
}
|
||||
|
||||
function useSubscriptionInternal() {
|
||||
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
|
||||
const telemetry = useTelemetry()
|
||||
@@ -82,11 +74,22 @@ function useSubscriptionInternal() {
|
||||
() => subscriptionStatus.value?.subscription_tier ?? null
|
||||
)
|
||||
|
||||
const subscriptionDuration = computed(
|
||||
() => subscriptionStatus.value?.subscription_duration ?? null
|
||||
)
|
||||
|
||||
const isYearlySubscription = computed(
|
||||
() => subscriptionDuration.value === 'ANNUAL'
|
||||
)
|
||||
|
||||
const subscriptionTierName = computed(() => {
|
||||
const tier = subscriptionTier.value
|
||||
if (!tier) return ''
|
||||
const key = TIER_TO_I18N_KEY[tier] ?? 'standard'
|
||||
return t(`subscription.tiers.${key}.name`)
|
||||
const key = TIER_TO_KEY[tier] ?? 'standard'
|
||||
const baseName = t(`subscription.tiers.${key}.name`)
|
||||
return isYearlySubscription.value
|
||||
? t('subscription.tierNameYearly', { name: baseName })
|
||||
: baseName
|
||||
})
|
||||
|
||||
const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`
|
||||
@@ -241,6 +244,8 @@ function useSubscriptionInternal() {
|
||||
formattedRenewalDate,
|
||||
formattedEndDate,
|
||||
subscriptionTier,
|
||||
subscriptionDuration,
|
||||
isYearlySubscription,
|
||||
subscriptionTierName,
|
||||
subscriptionStatus,
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ interface TierFeatures {
|
||||
customLoRAs: boolean
|
||||
}
|
||||
|
||||
export const TIER_FEATURES: Record<TierKey, TierFeatures> = {
|
||||
const TIER_FEATURES: Record<TierKey, TierFeatures> = {
|
||||
standard: { customLoRAs: false },
|
||||
creator: { customLoRAs: true },
|
||||
pro: { customLoRAs: true },
|
||||
@@ -37,16 +37,20 @@ export const TIER_FEATURES: Record<TierKey, TierFeatures> = {
|
||||
|
||||
export const DEFAULT_TIER_KEY: TierKey = 'standard'
|
||||
|
||||
// Founder tier pricing: legacy tier with fixed values not in TIER_PRICING
|
||||
const FOUNDER_MONTHLY_PRICE = 20
|
||||
const FOUNDER_MONTHLY_CREDITS = 5460
|
||||
|
||||
export function getTierPrice(tierKey: TierKey): number {
|
||||
export function getTierPrice(tierKey: TierKey, isYearly = false): number {
|
||||
if (tierKey === 'founder') return FOUNDER_MONTHLY_PRICE
|
||||
return TIER_PRICING[tierKey].monthly
|
||||
const pricing = TIER_PRICING[tierKey]
|
||||
return isYearly ? pricing.yearly : pricing.monthly
|
||||
}
|
||||
|
||||
export function getTierCredits(tierKey: TierKey): number {
|
||||
if (tierKey === 'founder') return FOUNDER_MONTHLY_CREDITS
|
||||
return TIER_PRICING[tierKey].credits
|
||||
}
|
||||
|
||||
export function getTierFeatures(tierKey: TierKey): TierFeatures {
|
||||
return TIER_FEATURES[tierKey]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ const mockIsCancelled = ref(false)
|
||||
const mockSubscriptionTier = ref<
|
||||
'STANDARD' | 'CREATOR' | 'PRO' | 'FOUNDERS_EDITION' | null
|
||||
>('CREATOR')
|
||||
const mockIsYearlySubscription = ref(false)
|
||||
|
||||
const TIER_TO_NAME: Record<string, string> = {
|
||||
STANDARD: 'Standard',
|
||||
@@ -27,9 +28,12 @@ const mockSubscriptionData = {
|
||||
formattedRenewalDate: computed(() => '2024-12-31'),
|
||||
formattedEndDate: computed(() => '2024-12-31'),
|
||||
subscriptionTier: computed(() => mockSubscriptionTier.value),
|
||||
subscriptionTierName: computed(() =>
|
||||
mockSubscriptionTier.value ? TIER_TO_NAME[mockSubscriptionTier.value] : ''
|
||||
),
|
||||
subscriptionTierName: computed(() => {
|
||||
if (!mockSubscriptionTier.value) return ''
|
||||
const baseName = TIER_TO_NAME[mockSubscriptionTier.value]
|
||||
return mockIsYearlySubscription.value ? `${baseName} Yearly` : baseName
|
||||
}),
|
||||
isYearlySubscription: computed(() => mockIsYearlySubscription.value),
|
||||
handleInvoiceHistory: vi.fn()
|
||||
}
|
||||
|
||||
@@ -212,6 +216,7 @@ describe('SubscriptionPanel', () => {
|
||||
mockIsActiveSubscription.value = false
|
||||
mockIsCancelled.value = false
|
||||
mockSubscriptionTier.value = 'CREATOR'
|
||||
mockIsYearlySubscription.value = false
|
||||
})
|
||||
|
||||
describe('subscription state functionality', () => {
|
||||
|
||||
Reference in New Issue
Block a user