mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-10 22:40:00 +00:00
Compare commits
8 Commits
dev/remote
...
fix/top-up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8f563d3f8 | ||
|
|
8ce0c9ea58 | ||
|
|
1ebdab9b22 | ||
|
|
6fb677feca | ||
|
|
5fd6aa69b8 | ||
|
|
cd15c2706d | ||
|
|
afaceb5dd8 | ||
|
|
9d889198f8 |
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<component
|
||||
:is="currentButton"
|
||||
:key="isActiveSubscription ? 'queue' : 'subscribe'"
|
||||
:key="isSubscriptionRequirementMet ? 'queue' : 'subscribe'"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -11,9 +11,9 @@ import ComfyQueueButton from '@/components/actionbar/ComfyRunButton/ComfyQueueBu
|
||||
import SubscribeToRunButton from '@/platform/cloud/subscription/components/SubscribeToRun.vue'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
const { isSubscriptionRequirementMet } = useSubscription()
|
||||
|
||||
const currentButton = computed(() =>
|
||||
isActiveSubscription.value ? ComfyQueueButton : SubscribeToRunButton
|
||||
isSubscriptionRequirementMet.value ? ComfyQueueButton : SubscribeToRunButton
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<UserCredit text-class="text-3xl font-bold" />
|
||||
<Skeleton v-if="loading" width="2rem" height="2rem" />
|
||||
<Button
|
||||
v-else-if="isActiveSubscription"
|
||||
v-else-if="isSubscriptionRequirementMet"
|
||||
:label="$t('credits.purchaseCredits')"
|
||||
:loading="loading"
|
||||
@click="handlePurchaseCreditsClick"
|
||||
@@ -125,6 +125,7 @@ import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.v
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useExternalLink } from '@/composables/useExternalLink'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
@@ -144,7 +145,11 @@ const authStore = useFirebaseAuthStore()
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const commandStore = useCommandStore()
|
||||
const telemetry = useTelemetry()
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
const subscription = isCloud ? useSubscription() : null
|
||||
const isSubscriptionRequirementMet = computed(() => {
|
||||
if (!isCloud) return true
|
||||
return subscription?.isSubscriptionRequirementMet.value ?? false
|
||||
})
|
||||
const loading = computed(() => authStore.loading)
|
||||
const balanceLoading = computed(() => authStore.isFetchingBalance)
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
|
||||
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
||||
useSubscription: vi.fn(() => ({
|
||||
isActiveSubscription: { value: true },
|
||||
isSubscriptionRequirementMet: { value: true },
|
||||
fetchStatus: mockFetchStatus
|
||||
}))
|
||||
}))
|
||||
@@ -121,6 +121,11 @@ describe('CurrentUserPopover', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const findButtonByLabel = (wrapper: VueWrapper, label: string) =>
|
||||
wrapper
|
||||
.findAllComponents(Button)
|
||||
.find((button) => button.props('label') === label)!
|
||||
|
||||
const mountComponent = (): VueWrapper => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
@@ -161,8 +166,7 @@ describe('CurrentUserPopover', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Find all buttons and get the settings button (third button)
|
||||
const buttons = wrapper.findAllComponents(Button)
|
||||
const settingsButton = buttons[2]
|
||||
const settingsButton = findButtonByLabel(wrapper, 'User Settings')
|
||||
|
||||
// Click the settings button
|
||||
await settingsButton.trigger('click')
|
||||
@@ -179,8 +183,7 @@ describe('CurrentUserPopover', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Find all buttons and get the logout button (last button)
|
||||
const buttons = wrapper.findAllComponents(Button)
|
||||
const logoutButton = buttons[4]
|
||||
const logoutButton = findButtonByLabel(wrapper, 'Log Out')
|
||||
|
||||
// Click the logout button
|
||||
await logoutButton.trigger('click')
|
||||
@@ -197,8 +200,10 @@ describe('CurrentUserPopover', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Find all buttons and get the Partner Nodes info button (first one)
|
||||
const buttons = wrapper.findAllComponents(Button)
|
||||
const partnerNodesButton = buttons[0]
|
||||
const partnerNodesButton = findButtonByLabel(
|
||||
wrapper,
|
||||
'Partner Nodes pricing table'
|
||||
)
|
||||
|
||||
// Click the Partner Nodes button
|
||||
await partnerNodesButton.trigger('click')
|
||||
@@ -218,8 +223,7 @@ describe('CurrentUserPopover', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Find all buttons and get the top-up button (second one)
|
||||
const buttons = wrapper.findAllComponents(Button)
|
||||
const topUpButton = buttons[1]
|
||||
const topUpButton = findButtonByLabel(wrapper, 'Top Up')
|
||||
|
||||
// Click the top-up button
|
||||
await topUpButton.trigger('click')
|
||||
|
||||
@@ -23,37 +23,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isActiveSubscription" class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-1">
|
||||
<UserCredit text-class="text-2xl" />
|
||||
<component
|
||||
:is="SubscriptionSection"
|
||||
v-if="SubscriptionSection"
|
||||
@top-up="handleTopUp"
|
||||
@open-partner-info="handleOpenPartnerNodesInfo"
|
||||
@open-plan-settings="handleOpenPlanAndCreditsSettings"
|
||||
/>
|
||||
<template v-else>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-1">
|
||||
<UserCredit text-class="text-2xl" />
|
||||
<Button
|
||||
:label="$t('subscription.partnerNodesCredits')"
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
class="pl-6 p-0 h-auto justify-start"
|
||||
:pt="{
|
||||
root: {
|
||||
class: 'hover:bg-transparent active:bg-transparent'
|
||||
}
|
||||
}"
|
||||
@click="handleOpenPartnerNodesInfo"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
:label="$t('subscription.partnerNodesCredits')"
|
||||
:label="$t('credits.topUp.topUp')"
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
class="pl-6 p-0 h-auto justify-start"
|
||||
:pt="{
|
||||
root: {
|
||||
class: 'hover:bg-transparent active:bg-transparent'
|
||||
}
|
||||
}"
|
||||
@click="handleOpenPartnerNodesInfo"
|
||||
@click="handleTopUp"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
:label="$t('credits.topUp.topUp')"
|
||||
class="justify-start"
|
||||
:label="$t(planSettingsLabel)"
|
||||
icon="pi pi-receipt"
|
||||
text
|
||||
fluid
|
||||
severity="secondary"
|
||||
size="small"
|
||||
@click="handleTopUp"
|
||||
@click="handleOpenPlanAndCreditsSettings"
|
||||
/>
|
||||
</div>
|
||||
<SubscribeButton
|
||||
v-else
|
||||
:label="$t('subscription.subscribeToComfyCloud')"
|
||||
size="small"
|
||||
variant="gradient"
|
||||
@subscribed="handleSubscribed"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<Divider class="my-2" />
|
||||
|
||||
@@ -67,17 +79,6 @@
|
||||
@click="handleOpenUserSettings"
|
||||
/>
|
||||
|
||||
<Button
|
||||
v-if="isActiveSubscription"
|
||||
class="justify-start"
|
||||
:label="$t(planSettingsLabel)"
|
||||
icon="pi pi-receipt"
|
||||
text
|
||||
fluid
|
||||
severity="secondary"
|
||||
@click="handleOpenPlanAndCreditsSettings"
|
||||
/>
|
||||
|
||||
<Divider class="my-2" />
|
||||
|
||||
<Button
|
||||
@@ -95,15 +96,13 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import { onMounted } from 'vue'
|
||||
import { defineAsyncComponent, onMounted } from 'vue'
|
||||
|
||||
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 { useExternalLink } from '@/composables/useExternalLink'
|
||||
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
@@ -114,15 +113,18 @@ const emit = defineEmits<{
|
||||
|
||||
const { buildDocsUrl } = useExternalLink()
|
||||
|
||||
const planSettingsLabel = isCloud
|
||||
? 'settingsCategories.PlanCredits'
|
||||
: 'settingsCategories.Credits'
|
||||
const planSettingsLabel = 'settingsCategories.Credits'
|
||||
|
||||
const SubscriptionSection = isCloud
|
||||
? defineAsyncComponent(
|
||||
() => import('./CurrentUserPopoverSubscriptionSection.vue')
|
||||
)
|
||||
: null
|
||||
|
||||
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
|
||||
useCurrentUser()
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const dialogService = useDialogService()
|
||||
const { isActiveSubscription, fetchStatus } = useSubscription()
|
||||
|
||||
const handleOpenUserSettings = () => {
|
||||
dialogService.showSettingsDialog('user')
|
||||
@@ -161,10 +163,6 @@ const handleLogout = async () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleSubscribed = async () => {
|
||||
await fetchStatus()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void authActions.fetchBalance()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div v-if="isSubscriptionRequirementMet" class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-1">
|
||||
<UserCredit text-class="text-2xl" />
|
||||
<Button
|
||||
:label="$t('subscription.partnerNodesCredits')"
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
class="pl-6 p-0 h-auto justify-start"
|
||||
:pt="{
|
||||
root: {
|
||||
class: 'hover:bg-transparent active:bg-transparent'
|
||||
}
|
||||
}"
|
||||
@click="handleOpenPartnerInfo"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
:label="$t('credits.topUp.topUp')"
|
||||
severity="secondary"
|
||||
size="small"
|
||||
@click="handleTopUp"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
class="justify-start"
|
||||
:label="$t('settingsCategories.PlanCredits')"
|
||||
icon="pi pi-receipt"
|
||||
text
|
||||
fluid
|
||||
severity="secondary"
|
||||
@click="handleOpenPlanSettings"
|
||||
/>
|
||||
</div>
|
||||
<SubscribeButton
|
||||
v-else
|
||||
:label="$t('subscription.subscribeToComfyCloud')"
|
||||
size="small"
|
||||
variant="gradient"
|
||||
class="w-full"
|
||||
@subscribed="handleSubscribed"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
|
||||
import UserCredit from '@/components/common/UserCredit.vue'
|
||||
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
|
||||
const emit = defineEmits<{
|
||||
'top-up': []
|
||||
'open-partner-info': []
|
||||
'open-plan-settings': []
|
||||
}>()
|
||||
|
||||
const { isSubscriptionRequirementMet, fetchStatus } = useSubscription()
|
||||
|
||||
const handleSubscribed = async () => {
|
||||
await fetchStatus()
|
||||
}
|
||||
|
||||
const handleTopUp = () => {
|
||||
emit('top-up')
|
||||
}
|
||||
|
||||
const handleOpenPartnerInfo = () => {
|
||||
emit('open-partner-info')
|
||||
}
|
||||
|
||||
const handleOpenPlanSettings = () => {
|
||||
emit('open-plan-settings')
|
||||
}
|
||||
</script>
|
||||
@@ -6,7 +6,6 @@ import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import type { ErrorRecoveryStrategy } from '@/composables/useErrorHandling'
|
||||
import { t } from '@/i18n'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
@@ -82,8 +81,13 @@ export const useFirebaseAuthActions = () => {
|
||||
)
|
||||
|
||||
const purchaseCredits = wrapWithErrorHandlingAsync(async (amount: number) => {
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
if (!isActiveSubscription.value) return
|
||||
if (isCloud) {
|
||||
const { useSubscription } = await import(
|
||||
'@/platform/cloud/subscription/composables/useSubscription'
|
||||
)
|
||||
const { isSubscriptionRequirementMet } = useSubscription()
|
||||
if (!isSubscriptionRequirementMet.value) return
|
||||
}
|
||||
|
||||
const response = await authStore.initiateCreditPurchase({
|
||||
amount_micros: usdToMicros(amount),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
@@ -46,6 +48,7 @@ import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { useSubgraphStore } from '@/stores/subgraphStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
@@ -63,7 +66,8 @@ import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTyp
|
||||
|
||||
import { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelectorDialog'
|
||||
|
||||
const { isActiveSubscription, showSubscriptionDialog } = useSubscription()
|
||||
const defaultSubscriptionState = computed(() => true)
|
||||
const noop = () => {}
|
||||
|
||||
const moveSelectedNodesVersionAdded = '1.22.2'
|
||||
|
||||
@@ -85,6 +89,11 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
useSelectedLiteGraphItems()
|
||||
const getTracker = () => workflowStore.activeWorkflow?.changeTracker
|
||||
|
||||
const subscription = isCloud ? useSubscription() : null
|
||||
const subscriptionState =
|
||||
subscription?.isSubscriptionRequirementMet ?? defaultSubscriptionState
|
||||
const subscriptionDialog = subscription?.showSubscriptionDialog ?? noop
|
||||
|
||||
const moveSelectedNodes = (
|
||||
positionUpdater: (pos: Point, gridSize: number) => Point
|
||||
) => {
|
||||
@@ -475,8 +484,8 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}) => {
|
||||
useTelemetry()?.trackRunButton(metadata)
|
||||
if (!isActiveSubscription.value) {
|
||||
showSubscriptionDialog()
|
||||
if (!subscriptionState.value) {
|
||||
subscriptionDialog()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -498,8 +507,8 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}) => {
|
||||
useTelemetry()?.trackRunButton(metadata)
|
||||
if (!isActiveSubscription.value) {
|
||||
showSubscriptionDialog()
|
||||
if (!subscriptionState.value) {
|
||||
subscriptionDialog()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -520,8 +529,8 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
trigger_source?: ExecutionTriggerSource
|
||||
}) => {
|
||||
useTelemetry()?.trackRunButton(metadata)
|
||||
if (!isActiveSubscription.value) {
|
||||
showSubscriptionDialog()
|
||||
if (!subscriptionState.value) {
|
||||
subscriptionDialog()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ useExtensionService().registerExtension({
|
||||
|
||||
setup: async () => {
|
||||
const { isLoggedIn } = useCurrentUser()
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
const { isSubscriptionRequirementMet } = useSubscription()
|
||||
|
||||
watchDebounced(
|
||||
[isLoggedIn, isActiveSubscription],
|
||||
[isLoggedIn, isSubscriptionRequirementMet],
|
||||
() => {
|
||||
if (!isLoggedIn.value) return
|
||||
void refreshRemoteConfig()
|
||||
|
||||
@@ -51,7 +51,8 @@ const emit = defineEmits<{
|
||||
subscribed: []
|
||||
}>()
|
||||
|
||||
const { subscribe, isActiveSubscription, fetchStatus } = useSubscription()
|
||||
const { subscribe, isSubscriptionRequirementMet, fetchStatus } =
|
||||
useSubscription()
|
||||
const telemetry = useTelemetry()
|
||||
|
||||
const isLoading = ref(false)
|
||||
@@ -76,7 +77,7 @@ const startPollingSubscriptionStatus = () => {
|
||||
|
||||
await fetchStatus()
|
||||
|
||||
if (isActiveSubscription.value) {
|
||||
if (isSubscriptionRequirementMet.value) {
|
||||
stopPolling()
|
||||
telemetry?.trackMonthlySubscriptionSucceeded()
|
||||
emit('subscribed')
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-2xl font-inter font-semibold leading-tight">
|
||||
{{
|
||||
isActiveSubscription
|
||||
isSubscriptionRequirementMet
|
||||
? $t('subscription.title')
|
||||
: $t('subscription.titleUnsubscribed')
|
||||
}}
|
||||
@@ -27,7 +27,7 @@
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
v-if="isSubscriptionRequirementMet"
|
||||
class="text-sm text-text-secondary"
|
||||
>
|
||||
<template v-if="isCancelled">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
v-if="isActiveSubscription"
|
||||
v-if="isSubscriptionRequirementMet"
|
||||
:label="$t('subscription.manageSubscription')"
|
||||
severity="secondary"
|
||||
class="text-xs bg-interface-menu-component-surface-selected"
|
||||
@@ -196,7 +196,7 @@
|
||||
{{ $t('subscription.viewUsageHistory') }}
|
||||
</a>
|
||||
<Button
|
||||
v-if="isActiveSubscription"
|
||||
v-if="isSubscriptionRequirementMet"
|
||||
:label="$t('subscription.addCredits')"
|
||||
severity="secondary"
|
||||
class="p-2 min-h-8 bg-interface-menu-component-surface-selected"
|
||||
@@ -320,7 +320,7 @@ import { cn } from '@/utils/tailwindUtil'
|
||||
const { buildDocsUrl } = useExternalLink()
|
||||
|
||||
const {
|
||||
isActiveSubscription,
|
||||
isSubscriptionRequirementMet,
|
||||
isCancelled,
|
||||
formattedRenewalDate,
|
||||
formattedEndDate,
|
||||
|
||||
@@ -31,8 +31,12 @@ function useSubscriptionInternal() {
|
||||
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
|
||||
const telemetry = useTelemetry()
|
||||
|
||||
const isSubscribedOrIsNotCloud = computed(() => {
|
||||
if (!isCloud || !window.__CONFIG__?.subscription_required) return true
|
||||
const isSubscriptionCheckRequired = computed(() =>
|
||||
Boolean(isCloud && window.__CONFIG__?.subscription_required)
|
||||
)
|
||||
|
||||
const isSubscriptionRequirementMet = computed(() => {
|
||||
if (!isSubscriptionCheckRequired.value) return true
|
||||
|
||||
return subscriptionStatus.value?.is_active ?? false
|
||||
})
|
||||
@@ -106,12 +110,12 @@ function useSubscriptionInternal() {
|
||||
}
|
||||
|
||||
const shouldWatchCancellation = (): boolean =>
|
||||
Boolean(isCloud && window.__CONFIG__?.subscription_required)
|
||||
isSubscriptionCheckRequired.value
|
||||
|
||||
const { startCancellationWatcher, stopCancellationWatcher } =
|
||||
useSubscriptionCancellationWatcher({
|
||||
fetchStatus,
|
||||
isActiveSubscription: isSubscribedOrIsNotCloud,
|
||||
isSubscriptionRequirementMet,
|
||||
subscriptionStatus,
|
||||
telemetry,
|
||||
shouldWatchCancellation
|
||||
@@ -123,9 +127,11 @@ function useSubscriptionInternal() {
|
||||
}
|
||||
|
||||
const requireActiveSubscription = async (): Promise<void> => {
|
||||
if (!isSubscriptionCheckRequired.value) return
|
||||
|
||||
await fetchSubscriptionStatus()
|
||||
|
||||
if (!isSubscribedOrIsNotCloud.value) {
|
||||
if (!isSubscriptionRequirementMet.value) {
|
||||
showSubscriptionDialog()
|
||||
}
|
||||
}
|
||||
@@ -223,7 +229,7 @@ function useSubscriptionInternal() {
|
||||
|
||||
return {
|
||||
// State
|
||||
isActiveSubscription: isSubscribedOrIsNotCloud,
|
||||
isSubscriptionRequirementMet,
|
||||
isCancelled,
|
||||
formattedRenewalDate,
|
||||
formattedEndDate,
|
||||
|
||||
@@ -36,8 +36,8 @@ export function useSubscriptionActions() {
|
||||
void handleRefresh()
|
||||
})
|
||||
|
||||
const handleAddApiCredits = () => {
|
||||
dialogService.showTopUpCreditsDialog()
|
||||
const handleAddApiCredits = async () => {
|
||||
await dialogService.showTopUpCreditsDialog()
|
||||
}
|
||||
|
||||
const handleMessageSupport = async () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ const CANCELLATION_BACKOFF_MULTIPLIER = 3 // 5s, 15s, 45s, 135s intervals
|
||||
|
||||
type CancellationWatcherOptions = {
|
||||
fetchStatus: () => Promise<CloudSubscriptionStatusResponse | null | void>
|
||||
isActiveSubscription: ComputedRef<boolean>
|
||||
isSubscriptionRequirementMet: ComputedRef<boolean>
|
||||
subscriptionStatus: Ref<CloudSubscriptionStatusResponse | null>
|
||||
telemetry: Pick<TelemetryProvider, 'trackMonthlySubscriptionCancelled'> | null
|
||||
shouldWatchCancellation: () => boolean
|
||||
@@ -20,7 +20,7 @@ type CancellationWatcherOptions = {
|
||||
|
||||
export function useSubscriptionCancellationWatcher({
|
||||
fetchStatus,
|
||||
isActiveSubscription,
|
||||
isSubscriptionRequirementMet,
|
||||
subscriptionStatus,
|
||||
telemetry,
|
||||
shouldWatchCancellation
|
||||
@@ -73,7 +73,7 @@ export function useSubscriptionCancellationWatcher({
|
||||
try {
|
||||
await fetchStatus()
|
||||
|
||||
if (!isActiveSubscription.value) {
|
||||
if (!isSubscriptionRequirementMet.value) {
|
||||
if (!cancellationTracked.value) {
|
||||
cancellationTracked.value = true
|
||||
try {
|
||||
|
||||
@@ -676,11 +676,17 @@ export class ComfyApp {
|
||||
'Payment Required: Please add credits to your account to use this node.'
|
||||
)
|
||||
) {
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
if (isActiveSubscription.value) {
|
||||
if (!isCloud) {
|
||||
useDialogService().showTopUpCreditsDialog({
|
||||
isInsufficientCredits: true
|
||||
})
|
||||
} else {
|
||||
const { isSubscriptionRequirementMet } = useSubscription()
|
||||
if (isSubscriptionRequirementMet.value) {
|
||||
useDialogService().showTopUpCreditsDialog({
|
||||
isInsufficientCredits: true
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
useDialogService().showExecutionErrorDialog(detail)
|
||||
|
||||
@@ -17,7 +17,6 @@ import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.
|
||||
import { t } from '@/i18n'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
|
||||
import type { ExecutionErrorWsMessage } from '@/schemas/apiSchema'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
@@ -377,11 +376,16 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showTopUpCreditsDialog(options?: {
|
||||
async function showTopUpCreditsDialog(options?: {
|
||||
isInsufficientCredits?: boolean
|
||||
}) {
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
if (!isActiveSubscription.value) return
|
||||
if (isCloud) {
|
||||
const { useSubscription } = await import(
|
||||
'@/platform/cloud/subscription/composables/useSubscription'
|
||||
)
|
||||
const { isSubscriptionRequirementMet } = useSubscription()
|
||||
if (!isSubscriptionRequirementMet.value) return
|
||||
}
|
||||
|
||||
return dialogStore.showDialog({
|
||||
key: 'top-up-credits',
|
||||
|
||||
@@ -100,7 +100,7 @@ vi.mock('@/composables/auth/useFirebaseAuthActions', () => ({
|
||||
|
||||
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
||||
useSubscription: vi.fn(() => ({
|
||||
isActiveSubscription: vi.fn().mockReturnValue(true),
|
||||
isSubscriptionRequirementMet: { value: true },
|
||||
showSubscriptionDialog: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
@@ -7,7 +7,7 @@ import SubscriptionPanel from '@/platform/cloud/subscription/components/Subscrip
|
||||
|
||||
// Mock composables
|
||||
const mockSubscriptionData = {
|
||||
isActiveSubscription: false,
|
||||
isSubscriptionRequirementMet: false,
|
||||
isCancelled: false,
|
||||
formattedRenewalDate: '2024-12-31',
|
||||
formattedEndDate: '2024-12-31',
|
||||
@@ -120,14 +120,14 @@ describe('SubscriptionPanel', () => {
|
||||
|
||||
describe('subscription state functionality', () => {
|
||||
it('shows correct UI for active subscription', () => {
|
||||
mockSubscriptionData.isActiveSubscription = true
|
||||
mockSubscriptionData.isSubscriptionRequirementMet = true
|
||||
const wrapper = createWrapper()
|
||||
expect(wrapper.text()).toContain('Manage Subscription')
|
||||
expect(wrapper.text()).toContain('Add Credits')
|
||||
})
|
||||
|
||||
it('shows correct UI for inactive subscription', () => {
|
||||
mockSubscriptionData.isActiveSubscription = false
|
||||
mockSubscriptionData.isSubscriptionRequirementMet = false
|
||||
const wrapper = createWrapper()
|
||||
expect(wrapper.findComponent({ name: 'SubscribeButton' }).exists()).toBe(
|
||||
true
|
||||
@@ -137,14 +137,14 @@ describe('SubscriptionPanel', () => {
|
||||
})
|
||||
|
||||
it('shows renewal date for active non-cancelled subscription', () => {
|
||||
mockSubscriptionData.isActiveSubscription = true
|
||||
mockSubscriptionData.isSubscriptionRequirementMet = true
|
||||
mockSubscriptionData.isCancelled = false
|
||||
const wrapper = createWrapper()
|
||||
expect(wrapper.text()).toContain('Renews 2024-12-31')
|
||||
})
|
||||
|
||||
it('shows expiry date for cancelled subscription', () => {
|
||||
mockSubscriptionData.isActiveSubscription = true
|
||||
mockSubscriptionData.isSubscriptionRequirementMet = true
|
||||
mockSubscriptionData.isCancelled = true
|
||||
const wrapper = createWrapper()
|
||||
expect(wrapper.text()).toContain('Expires 2024-12-31')
|
||||
|
||||
@@ -92,7 +92,7 @@ describe('useSubscription', () => {
|
||||
})
|
||||
|
||||
describe('computed properties', () => {
|
||||
it('should compute isActiveSubscription correctly when subscription is active', async () => {
|
||||
it('should compute isSubscriptionRequirementMet correctly when subscription is active', async () => {
|
||||
vi.mocked(global.fetch).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
@@ -103,13 +103,13 @@ describe('useSubscription', () => {
|
||||
} as Response)
|
||||
|
||||
mockIsLoggedIn.value = true
|
||||
const { isActiveSubscription, fetchStatus } = useSubscription()
|
||||
const { isSubscriptionRequirementMet, fetchStatus } = useSubscription()
|
||||
|
||||
await fetchStatus()
|
||||
expect(isActiveSubscription.value).toBe(true)
|
||||
expect(isSubscriptionRequirementMet.value).toBe(true)
|
||||
})
|
||||
|
||||
it('should compute isActiveSubscription as false when subscription is inactive', async () => {
|
||||
it('should compute isSubscriptionRequirementMet as false when subscription is inactive', async () => {
|
||||
vi.mocked(global.fetch).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
@@ -120,10 +120,10 @@ describe('useSubscription', () => {
|
||||
} as Response)
|
||||
|
||||
mockIsLoggedIn.value = true
|
||||
const { isActiveSubscription, fetchStatus } = useSubscription()
|
||||
const { isSubscriptionRequirementMet, fetchStatus } = useSubscription()
|
||||
|
||||
await fetchStatus()
|
||||
expect(isActiveSubscription.value).toBe(false)
|
||||
expect(isSubscriptionRequirementMet.value).toBe(false)
|
||||
})
|
||||
|
||||
it('should format renewal date correctly', async () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('useSubscriptionCancellationWatcher', () => {
|
||||
baseStatus
|
||||
)
|
||||
const isActive = ref(true)
|
||||
const isActiveSubscription = computed(() => isActive.value)
|
||||
const isSubscriptionRequirementMet = computed(() => isActive.value)
|
||||
|
||||
let shouldWatch = true
|
||||
const shouldWatchCancellation = () => shouldWatch
|
||||
@@ -76,7 +76,7 @@ describe('useSubscriptionCancellationWatcher', () => {
|
||||
|
||||
const { startCancellationWatcher } = initWatcher({
|
||||
fetchStatus,
|
||||
isActiveSubscription,
|
||||
isSubscriptionRequirementMet,
|
||||
subscriptionStatus,
|
||||
telemetry: telemetryMock,
|
||||
shouldWatchCancellation
|
||||
@@ -106,7 +106,7 @@ describe('useSubscriptionCancellationWatcher', () => {
|
||||
|
||||
const { startCancellationWatcher } = initWatcher({
|
||||
fetchStatus,
|
||||
isActiveSubscription,
|
||||
isSubscriptionRequirementMet,
|
||||
subscriptionStatus,
|
||||
telemetry: telemetryMock,
|
||||
shouldWatchCancellation
|
||||
@@ -128,7 +128,7 @@ describe('useSubscriptionCancellationWatcher', () => {
|
||||
|
||||
const { startCancellationWatcher } = initWatcher({
|
||||
fetchStatus,
|
||||
isActiveSubscription,
|
||||
isSubscriptionRequirementMet,
|
||||
subscriptionStatus,
|
||||
telemetry: telemetryMock,
|
||||
shouldWatchCancellation
|
||||
@@ -153,7 +153,7 @@ describe('useSubscriptionCancellationWatcher', () => {
|
||||
|
||||
const { startCancellationWatcher } = initWatcher({
|
||||
fetchStatus,
|
||||
isActiveSubscription,
|
||||
isSubscriptionRequirementMet,
|
||||
subscriptionStatus,
|
||||
telemetry: telemetryMock,
|
||||
shouldWatchCancellation
|
||||
|
||||
Reference in New Issue
Block a user