subscription improve (#6339)

## Summary

Run keyboard shortcut bypasses paywall
Top Up button visible before paywall (confusing - hide until subscribed)
Question mark icon next to commercial models has no tooltip (hide until
added)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6339-subscription-improve-29a6d73d3650818e92c7f60eda01646a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
This commit is contained in:
Terry Jia
2025-10-28 16:00:27 -04:00
committed by GitHub
parent 6e4471ad62
commit e7f640b436
7 changed files with 86 additions and 15 deletions

View File

@@ -69,6 +69,22 @@ vi.mock('@/services/dialogService', () => ({
}))
}))
// Mock the firebaseAuthStore
vi.mock('@/stores/firebaseAuthStore', () => ({
useFirebaseAuthStore: vi.fn(() => ({
getAuthHeader: vi
.fn()
.mockResolvedValue({ Authorization: 'Bearer mock-token' })
}))
}))
// Mock the useSubscription composable
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: vi.fn(() => ({
isActiveSubscription: vi.fn().mockReturnValue(true)
}))
}))
// Mock UserAvatar component
vi.mock('@/components/common/UserAvatar.vue', () => ({
default: {

View File

@@ -67,7 +67,11 @@
</div>
<div class="flex items-center justify-between">
<UserCredit text-class="text-2xl" />
<Button :label="$t('credits.topUp.topUp')" @click="handleTopUp" />
<Button
v-if="isActiveSubscription"
:label="$t('credits.topUp.topUp')"
@click="handleTopUp"
/>
</div>
</div>
</div>
@@ -82,6 +86,7 @@ import UserAvatar from '@/components/common/UserAvatar.vue'
import UserCredit from '@/components/common/UserCredit.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useDialogService } from '@/services/dialogService'
const emit = defineEmits<{
@@ -92,6 +97,7 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
useCurrentUser()
const authActions = useFirebaseAuthActions()
const dialogService = useDialogService()
const { isActiveSubscription } = useSubscription()
const handleOpenUserSettings = () => {
dialogService.showSettingsDialog('user')

View File

@@ -21,6 +21,7 @@ import {
import type { Point } from '@/lib/litegraph/src/litegraph'
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
import { createModelNodeFromAsset } from '@/platform/assets/utils/createModelNodeFromAsset'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { SUPPORT_URL } from '@/platform/support/config'
@@ -63,6 +64,8 @@ import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTyp
import { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelectorDialog'
const { isActiveSubscription, showSubscriptionDialog } = useSubscription()
const moveSelectedNodesVersionAdded = '1.22.2'
export function useCoreCommands(): ComfyCommand[] {
@@ -453,6 +456,11 @@ export function useCoreCommands(): ComfyCommand[] {
versionAdded: '1.3.7',
category: 'essentials' as const,
function: async () => {
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
}
const batchCount = useQueueSettingsStore().batchCount
if (isCloud) {
@@ -469,6 +477,11 @@ export function useCoreCommands(): ComfyCommand[] {
versionAdded: '1.3.7',
category: 'essentials' as const,
function: async () => {
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
}
const batchCount = useQueueSettingsStore().batchCount
if (isCloud) {
@@ -484,6 +497,11 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Queue Selected Output Nodes',
versionAdded: '1.19.6',
function: async () => {
if (!isActiveSubscription.value) {
showSubscriptionDialog()
return
}
const batchCount = useQueueSettingsStore().batchCount
const selectedNodes = getSelectedNodes()
const selectedOutputNodes = filterOutputNodes(selectedNodes)

View File

@@ -1640,6 +1640,7 @@
"beta": "BETA",
"perMonth": "USD / month",
"renewsDate": "Renews {date}",
"expiresDate": "Expires {date}",
"manageSubscription": "Manage subscription",
"apiNodesBalance": "\"API Nodes\" Credit Balance",
"apiNodesDescription": "For running commercial/proprietary models",

View File

@@ -23,11 +23,20 @@
<span>{{ $t('subscription.perMonth') }}</span>
</div>
<div v-if="isActiveSubscription" class="text-xs text-muted">
{{
$t('subscription.renewsDate', {
date: formattedRenewalDate
})
}}
<template v-if="isCancelled">
{{
$t('subscription.expiresDate', {
date: formattedEndDate
})
}}
</template>
<template v-else>
{{
$t('subscription.renewsDate', {
date: formattedRenewalDate
})
}}
</template>
</div>
</div>
<Button
@@ -58,14 +67,6 @@
<div class="text-xs text-muted">
{{ $t('subscription.apiNodesDescription') }}
</div>
<Button
icon="pi pi-question-circle"
text
rounded
size="small"
severity="secondary"
class="h-5 w-5"
/>
</div>
</div>
@@ -137,6 +138,7 @@
@click="handleViewUsageHistory"
/>
<Button
v-if="isActiveSubscription"
:label="$t('subscription.addApiCredits')"
severity="secondary"
class="text-xs"
@@ -219,7 +221,9 @@ const customerEventService = useCustomerEventsService()
const {
isActiveSubscription,
isCancelled,
formattedRenewalDate,
formattedEndDate,
formattedMonthlyPrice,
manageSubscription,
handleViewUsageHistory,

View File

@@ -21,7 +21,8 @@ interface CloudSubscriptionCheckoutResponse {
interface CloudSubscriptionStatusResponse {
is_active: boolean
subscription_id: string
renewal_date: string
renewal_date: string | null
end_date?: string | null
}
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
@@ -44,6 +45,10 @@ export function useSubscription() {
const { isLoggedIn } = useCurrentUser()
const isCancelled = computed(() => {
return !!subscriptionStatus.value?.end_date
})
const formattedRenewalDate = computed(() => {
if (!subscriptionStatus.value?.renewal_date) return ''
@@ -56,6 +61,18 @@ export function useSubscription() {
})
})
const formattedEndDate = computed(() => {
if (!subscriptionStatus.value?.end_date) return ''
const endDate = new Date(subscriptionStatus.value.end_date)
return endDate.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
})
const formattedMonthlyPrice = computed(
() => `$${MONTHLY_SUBSCRIPTION_PRICE.toFixed(0)}`
)
@@ -197,7 +214,9 @@ export function useSubscription() {
return {
// State
isActiveSubscription,
isCancelled,
formattedRenewalDate,
formattedEndDate,
formattedMonthlyPrice,
// Actions

View File

@@ -85,6 +85,13 @@ vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({
useFirebaseAuthActions: vi.fn(() => ({}))
}))
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: vi.fn(() => ({
isActiveSubscription: vi.fn().mockReturnValue(true),
showSubscriptionDialog: vi.fn()
}))
}))
describe('useCoreCommands', () => {
const mockSubgraph = {
nodes: [