Compare commits

...

1 Commits

Author SHA1 Message Date
bymyself
944e0e8118 fix(#11077): remove dead /cloud/subscribe route
Remove the unused cloud-subscribe route, its component
(CloudSubscriptionRedirectView.vue), and associated test file.
No internal navigation path references this route.
2026-04-10 06:51:39 +00:00
3 changed files with 0 additions and 299 deletions

View File

@@ -1,165 +0,0 @@
import { render, screen } from '@testing-library/vue'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import CloudSubscriptionRedirectView from './CloudSubscriptionRedirectView.vue'
const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0))
// Router mocks
let mockQuery: Record<string, unknown> = {}
const mockRouterPush = vi.fn()
vi.mock('vue-router', () => ({
useRoute: () => ({
query: mockQuery
}),
useRouter: () => ({
push: mockRouterPush
})
}))
// Firebase / subscription mocks
const authActionMocks = vi.hoisted(() => ({
reportError: vi.fn(),
accessBillingPortal: vi.fn()
}))
vi.mock('@/composables/auth/useAuthActions', () => ({
useAuthActions: () => authActionMocks
}))
vi.mock('@/composables/useErrorHandling', () => ({
useErrorHandling: () => ({
wrapWithErrorHandlingAsync:
<T extends (...args: never[]) => unknown>(fn: T) =>
(...args: Parameters<T>) =>
fn(...args)
})
}))
const subscriptionMocks = vi.hoisted(() => ({
isActiveSubscription: { value: false },
isInitialized: { value: true },
subscriptionStatus: { value: null }
}))
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: () => subscriptionMocks
}))
vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: () => subscriptionMocks
}))
// Avoid real network / isCloud behavior
const mockPerformSubscriptionCheckout = vi.fn()
vi.mock('@/platform/cloud/subscription/utils/subscriptionCheckoutUtil', () => ({
performSubscriptionCheckout: (...args: unknown[]) =>
mockPerformSubscriptionCheckout(...args)
}))
const createI18nInstance = () =>
createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
cloudOnboarding: {
skipToCloudApp: 'Skip to the cloud app'
},
g: {
comfyOrgLogoAlt: 'Comfy org logo'
},
subscription: {
subscribeTo: 'Subscribe to {plan}',
tiers: {
standard: { name: 'Standard' },
creator: { name: 'Creator' },
pro: { name: 'Pro' }
}
}
}
}
})
const mountView = async (query: Record<string, unknown>) => {
mockQuery = query
const { container } = render(CloudSubscriptionRedirectView, {
global: {
plugins: [createI18nInstance()]
}
})
await flushPromises()
return { container }
}
describe('CloudSubscriptionRedirectView', () => {
beforeEach(() => {
vi.clearAllMocks()
mockQuery = {}
subscriptionMocks.isActiveSubscription.value = false
subscriptionMocks.isInitialized.value = true
})
test('redirects to home when subscriptionType is missing', async () => {
await mountView({})
expect(mockRouterPush).toHaveBeenCalledWith('/')
})
test('redirects to home when subscriptionType is invalid', async () => {
await mountView({ tier: 'invalid' })
expect(mockRouterPush).toHaveBeenCalledWith('/')
})
test('shows subscription copy when subscriptionType is valid', async () => {
await mountView({ tier: 'creator' })
// Should not redirect to home
expect(mockRouterPush).not.toHaveBeenCalledWith('/')
// Shows copy under logo
expect(screen.getByText('Subscribe to Creator')).toBeInTheDocument()
// Triggers checkout flow
expect(mockPerformSubscriptionCheckout).toHaveBeenCalledWith(
'creator',
'monthly',
false
)
// Shows loading affordances
expect(
screen.getByRole('link', { name: /skip to the cloud app/i })
).toBeInTheDocument()
})
test('opens billing portal when subscription is already active', async () => {
subscriptionMocks.isActiveSubscription.value = true
await mountView({ tier: 'creator' })
expect(mockRouterPush).not.toHaveBeenCalledWith('/')
expect(authActionMocks.accessBillingPortal).toHaveBeenCalledTimes(1)
expect(mockPerformSubscriptionCheckout).not.toHaveBeenCalled()
})
test('uses first value when subscriptionType is an array', async () => {
await mountView({
tier: ['creator', 'pro']
})
expect(mockRouterPush).not.toHaveBeenCalledWith('/')
expect(screen.getByText('Subscribe to Creator')).toBeInTheDocument()
expect(mockPerformSubscriptionCheckout).toHaveBeenCalledWith(
'creator',
'monthly',
false
)
})
})

View File

@@ -1,127 +0,0 @@
<script setup lang="ts">
import Button from 'primevue/button'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useAuthActions } from '@/composables/auth/useAuthActions'
import { useErrorHandling } from '@/composables/useErrorHandling'
import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing'
import { performSubscriptionCheckout } from '@/platform/cloud/subscription/utils/subscriptionCheckoutUtil'
import type { BillingCycle } from '../subscription/utils/subscriptionTierRank'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const { reportError, accessBillingPortal } = useAuthActions()
const { wrapWithErrorHandlingAsync } = useErrorHandling()
const { isActiveSubscription, isInitialized, initialize } = useBillingContext()
const selectedTierKey = ref<TierKey | null>(null)
const tierDisplayName = computed(() => {
if (!selectedTierKey.value) return ''
const names: Record<TierKey, string> = {
free: t('subscription.tiers.free.name'),
standard: t('subscription.tiers.standard.name'),
creator: t('subscription.tiers.creator.name'),
pro: t('subscription.tiers.pro.name'),
founder: t('subscription.tiers.founder.name')
}
return names[selectedTierKey.value]
})
const runRedirect = wrapWithErrorHandlingAsync(async () => {
const rawType = route.query.tier
const rawCycle = route.query.cycle
let tierKeyParam: string | null = null
let cycleParam = 'monthly'
if (typeof rawType === 'string') {
tierKeyParam = rawType
} else if (Array.isArray(rawType) && rawType[0]) {
tierKeyParam = rawType[0]
}
if (typeof rawCycle === 'string') {
cycleParam = rawCycle
} else if (Array.isArray(rawCycle) && rawCycle[0]) {
cycleParam = rawCycle[0]
}
if (!tierKeyParam) {
await router.push('/')
return
}
// Only paid tiers can be checked out via redirect
const validTierKeys: TierKey[] = ['standard', 'creator', 'pro', 'founder']
if (!(validTierKeys as string[]).includes(tierKeyParam)) {
await router.push('/')
return
}
const tierKey = tierKeyParam as TierKey
selectedTierKey.value = tierKey
const validCycles: BillingCycle[] = ['monthly', 'yearly']
if (!cycleParam || !(validCycles as string[]).includes(cycleParam)) {
cycleParam = 'monthly'
}
if (!isInitialized.value) {
await initialize()
}
if (isActiveSubscription.value) {
await accessBillingPortal(undefined, false)
} else {
await performSubscriptionCheckout(
tierKey,
cycleParam as BillingCycle,
false
)
}
}, reportError)
onMounted(() => {
void runRedirect()
})
</script>
<template>
<div
class="bg-comfy-menu-secondary-bg flex size-full items-center justify-center"
>
<div class="flex flex-col items-center gap-4">
<img
src="/assets/images/comfy-logo-single.svg"
:alt="t('g.comfyOrgLogoAlt')"
class="size-16"
/>
<p
v-if="selectedTierKey"
class="font-inter text-base/normal font-normal text-base-foreground"
>
{{
t('subscription.subscribeTo', {
plan: tierDisplayName
})
}}
</p>
<ProgressSpinner v-if="selectedTierKey" class="size-8" stroke-width="4" />
<Button
v-if="selectedTierKey"
as="a"
href="/"
link
:label="t('cloudOnboarding.skipToCloudApp')"
/>
</div>
</div>
</template>

View File

@@ -77,13 +77,6 @@ export const cloudOnboardingRoutes: RouteRecordRaw[] = [
component: () =>
import('@/platform/cloud/onboarding/CloudAuthTimeoutView.vue'),
props: true
},
{
path: 'subscribe',
name: 'cloud-subscribe',
component: () =>
import('@/platform/cloud/onboarding/CloudSubscriptionRedirectView.vue'),
meta: { requiresAuth: true }
}
]
}