mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
[backport cloud/1.35] feat: add pricing table to user popover (#7593)
Backport of #7583 to `cloud/1.35` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7593-backport-cloud-1-35-feat-add-pricing-table-to-user-popover-2cc6d73d365081e4a4d2d7f22068d769) by [Unito](https://www.unito.io) Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
This commit is contained in:
@@ -18,7 +18,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Popover ref="popover" :show-arrow="false">
|
<Popover
|
||||||
|
ref="popover"
|
||||||
|
:show-arrow="false"
|
||||||
|
:pt="{
|
||||||
|
root: {
|
||||||
|
class: 'rounded-lg'
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
<CurrentUserPopover @close="closePopover" />
|
<CurrentUserPopover @close="closePopover" />
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -85,10 +85,24 @@ const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
|
|||||||
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
||||||
useSubscription: vi.fn(() => ({
|
useSubscription: vi.fn(() => ({
|
||||||
isActiveSubscription: { value: true },
|
isActiveSubscription: { value: true },
|
||||||
|
subscriptionTierName: { value: 'Creator' },
|
||||||
|
subscriptionTier: { value: 'CREATOR' },
|
||||||
fetchStatus: mockFetchStatus
|
fetchStatus: mockFetchStatus
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Mock the useSubscriptionDialog composable
|
||||||
|
const mockSubscriptionDialogShow = vi.fn()
|
||||||
|
vi.mock(
|
||||||
|
'@/platform/cloud/subscription/composables/useSubscriptionDialog',
|
||||||
|
() => ({
|
||||||
|
useSubscriptionDialog: vi.fn(() => ({
|
||||||
|
show: mockSubscriptionDialogShow,
|
||||||
|
hide: vi.fn()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// Mock UserAvatar component
|
// Mock UserAvatar component
|
||||||
vi.mock('@/components/common/UserAvatar.vue', () => ({
|
vi.mock('@/components/common/UserAvatar.vue', () => ({
|
||||||
default: {
|
default: {
|
||||||
@@ -272,4 +286,22 @@ describe('CurrentUserPopover', () => {
|
|||||||
expect(wrapper.emitted('close')).toBeTruthy()
|
expect(wrapper.emitted('close')).toBeTruthy()
|
||||||
expect(wrapper.emitted('close')!.length).toBe(1)
|
expect(wrapper.emitted('close')!.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('opens subscription dialog and emits close event when plans & pricing item is clicked', async () => {
|
||||||
|
const wrapper = mountComponent()
|
||||||
|
|
||||||
|
const plansPricingItem = wrapper.find(
|
||||||
|
'[data-testid="plans-pricing-menu-item"]'
|
||||||
|
)
|
||||||
|
expect(plansPricingItem.exists()).toBe(true)
|
||||||
|
|
||||||
|
await plansPricingItem.trigger('click')
|
||||||
|
|
||||||
|
// Verify subscription dialog show was called
|
||||||
|
expect(mockSubscriptionDialogShow).toHaveBeenCalled()
|
||||||
|
|
||||||
|
// Verify close event was emitted
|
||||||
|
expect(wrapper.emitted('close')).toBeTruthy()
|
||||||
|
expect(wrapper.emitted('close')!.length).toBe(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,9 +21,12 @@
|
|||||||
<p v-if="userEmail" class="my-0 truncate text-sm text-muted">
|
<p v-if="userEmail" class="my-0 truncate text-sm text-muted">
|
||||||
{{ userEmail }}
|
{{ userEmail }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="subscriptionTierName" class="my-0 truncate text-sm text-muted">
|
<span
|
||||||
|
v-if="subscriptionTierName"
|
||||||
|
class="my-0 text-xs text-foreground bg-secondary-background-hover rounded-full uppercase px-2 py-0.5 font-bold mt-2"
|
||||||
|
>
|
||||||
{{ subscriptionTierName }}
|
{{ subscriptionTierName }}
|
||||||
</p>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Credits Section -->
|
<!-- Credits Section -->
|
||||||
@@ -33,11 +36,16 @@
|
|||||||
v-if="authStore.isFetchingBalance"
|
v-if="authStore.isFetchingBalance"
|
||||||
width="4rem"
|
width="4rem"
|
||||||
height="1.25rem"
|
height="1.25rem"
|
||||||
class="flex-1"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
<span v-else class="text-base font-normal text-base-foreground flex-1">{{
|
<span v-else class="text-base font-semibold text-base-foreground">{{
|
||||||
formattedBalance
|
formattedBalance
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<i
|
||||||
|
v-if="flags.subscriptionTiersEnabled"
|
||||||
|
v-tooltip="{ value: $t('credits.unified.tooltip'), showDelay: 300 }"
|
||||||
|
class="icon-[lucide--circle-help] cursor-help text-base text-muted-foreground mr-auto"
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
:label="$t('subscription.addCredits')"
|
:label="$t('subscription.addCredits')"
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
@@ -58,24 +66,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Credits info row -->
|
|
||||||
<div
|
|
||||||
v-if="flags.subscriptionTiersEnabled && isActiveSubscription"
|
|
||||||
class="flex items-center gap-2 px-4 py-0"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
v-tooltip="{
|
|
||||||
value: $t('credits.unified.tooltip'),
|
|
||||||
showDelay: 300,
|
|
||||||
hideDelay: 300
|
|
||||||
}"
|
|
||||||
class="icon-[lucide--circle-help] cursor-help text-xs text-muted-foreground"
|
|
||||||
/>
|
|
||||||
<span class="text-sm text-muted-foreground">{{
|
|
||||||
$t('credits.unified.message')
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider class="my-2 mx-0" />
|
<Divider class="my-2 mx-0" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -91,14 +81,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="isActiveSubscription"
|
|
||||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||||
data-testid="plan-credits-menu-item"
|
data-testid="plans-pricing-menu-item"
|
||||||
@click="handleOpenPlanAndCreditsSettings"
|
@click="handleOpenPlansAndPricing"
|
||||||
>
|
>
|
||||||
<i class="icon-[lucide--receipt-text] text-muted-foreground text-sm" />
|
<i class="icon-[lucide--receipt-text] text-muted-foreground text-sm" />
|
||||||
<span class="text-sm text-base-foreground flex-1">{{
|
<span class="text-sm text-base-foreground flex-1">{{
|
||||||
$t(planSettingsLabel)
|
$t('subscription.plansAndPricing')
|
||||||
|
}}</span>
|
||||||
|
<span
|
||||||
|
v-if="canUpgrade"
|
||||||
|
class="text-xs font-bold text-base-background bg-base-foreground px-1.5 py-0.5 rounded-full"
|
||||||
|
>
|
||||||
|
{{ $t('subscription.upgrade') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isActiveSubscription"
|
||||||
|
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||||
|
data-testid="manage-plan-menu-item"
|
||||||
|
@click="handleOpenPlanAndCreditsSettings"
|
||||||
|
>
|
||||||
|
<i class="icon-[lucide--file-text] text-muted-foreground text-sm" />
|
||||||
|
<span class="text-sm text-base-foreground flex-1">{{
|
||||||
|
$t('subscription.managePlan')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -109,7 +116,7 @@
|
|||||||
>
|
>
|
||||||
<i class="icon-[lucide--settings-2] text-muted-foreground text-sm" />
|
<i class="icon-[lucide--settings-2] text-muted-foreground text-sm" />
|
||||||
<span class="text-sm text-base-foreground flex-1">{{
|
<span class="text-sm text-base-foreground flex-1">{{
|
||||||
$t('userSettings.title')
|
$t('userSettings.accountSettings')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -143,6 +150,7 @@ import { useExternalLink } from '@/composables/useExternalLink'
|
|||||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||||
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
||||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||||
|
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import { useTelemetry } from '@/platform/telemetry'
|
import { useTelemetry } from '@/platform/telemetry'
|
||||||
import { useDialogService } from '@/services/dialogService'
|
import { useDialogService } from '@/services/dialogService'
|
||||||
@@ -154,17 +162,18 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const { buildDocsUrl } = useExternalLink()
|
const { buildDocsUrl } = useExternalLink()
|
||||||
|
|
||||||
const planSettingsLabel = isCloud
|
|
||||||
? 'settingsCategories.PlanCredits'
|
|
||||||
: 'settingsCategories.Credits'
|
|
||||||
|
|
||||||
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
|
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
|
||||||
useCurrentUser()
|
useCurrentUser()
|
||||||
const authActions = useFirebaseAuthActions()
|
const authActions = useFirebaseAuthActions()
|
||||||
const authStore = useFirebaseAuthStore()
|
const authStore = useFirebaseAuthStore()
|
||||||
const dialogService = useDialogService()
|
const dialogService = useDialogService()
|
||||||
const { isActiveSubscription, subscriptionTierName, fetchStatus } =
|
const {
|
||||||
useSubscription()
|
isActiveSubscription,
|
||||||
|
subscriptionTierName,
|
||||||
|
subscriptionTier,
|
||||||
|
fetchStatus
|
||||||
|
} = useSubscription()
|
||||||
|
const subscriptionDialog = useSubscriptionDialog()
|
||||||
const { flags } = useFeatureFlags()
|
const { flags } = useFeatureFlags()
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
|
|
||||||
@@ -181,11 +190,21 @@ const formattedBalance = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const canUpgrade = computed(() => {
|
||||||
|
const tier = subscriptionTier.value
|
||||||
|
return tier === 'STANDARD' || tier === 'CREATOR'
|
||||||
|
})
|
||||||
|
|
||||||
const handleOpenUserSettings = () => {
|
const handleOpenUserSettings = () => {
|
||||||
dialogService.showSettingsDialog('user')
|
dialogService.showSettingsDialog('user')
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenPlansAndPricing = () => {
|
||||||
|
subscriptionDialog.show()
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
const handleOpenPlanAndCreditsSettings = () => {
|
const handleOpenPlanAndCreditsSettings = () => {
|
||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
dialogService.showSettingsDialog('subscription')
|
dialogService.showSettingsDialog('subscription')
|
||||||
|
|||||||
@@ -2018,6 +2018,9 @@
|
|||||||
"contactUs": "Contact us",
|
"contactUs": "Contact us",
|
||||||
"viewEnterprise": "View enterprise",
|
"viewEnterprise": "View enterprise",
|
||||||
"partnerNodesCredits": "Partner nodes pricing",
|
"partnerNodesCredits": "Partner nodes pricing",
|
||||||
|
"plansAndPricing": "Plans & pricing",
|
||||||
|
"managePlan": "Manage plan",
|
||||||
|
"upgrade": "UPGRADE",
|
||||||
"mostPopular": "Most popular",
|
"mostPopular": "Most popular",
|
||||||
"currentPlan": "Current Plan",
|
"currentPlan": "Current Plan",
|
||||||
"subscribeTo": "Subscribe to {plan}",
|
"subscribeTo": "Subscribe to {plan}",
|
||||||
@@ -2046,6 +2049,7 @@
|
|||||||
},
|
},
|
||||||
"userSettings": {
|
"userSettings": {
|
||||||
"title": "My Account Settings",
|
"title": "My Account Settings",
|
||||||
|
"accountSettings": "Account settings",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"provider": "Sign-in Provider",
|
"provider": "Sign-in Provider",
|
||||||
|
|||||||
Reference in New Issue
Block a user