mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 10:59:53 +00:00
feat: add pricing table to user popover (#7583)
## Summary Add ability to show pricing table from user popover. Misc style changes and logic. ## Changes - **What**: CurrentUserPopover.vue - **Breaking**: <!-- Any breaking changes (if none, remove this line) --> - **Dependencies**: <!-- New dependencies (if none, remove this line) --> ## Screenshots (if applicable) <img width="481" height="672" alt="image" src="https://github.com/user-attachments/assets/3dd55bc4-b49e-4b4e-920e-c97cabaa448a" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7583-feat-add-pricing-table-to-user-popover-2cc6d73d365081e1bbf1f2b351e53289) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -18,7 +18,15 @@
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Popover ref="popover" :show-arrow="false">
|
||||
<Popover
|
||||
ref="popover"
|
||||
:show-arrow="false"
|
||||
:pt="{
|
||||
root: {
|
||||
class: 'rounded-lg'
|
||||
}
|
||||
}"
|
||||
>
|
||||
<CurrentUserPopover @close="closePopover" />
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
@@ -85,10 +85,24 @@ const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
|
||||
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
|
||||
useSubscription: vi.fn(() => ({
|
||||
isActiveSubscription: { value: true },
|
||||
subscriptionTierName: { value: 'Creator' },
|
||||
subscriptionTier: { value: 'CREATOR' },
|
||||
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
|
||||
vi.mock('@/components/common/UserAvatar.vue', () => ({
|
||||
default: {
|
||||
@@ -272,4 +286,22 @@ describe('CurrentUserPopover', () => {
|
||||
expect(wrapper.emitted('close')).toBeTruthy()
|
||||
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">
|
||||
{{ userEmail }}
|
||||
</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 }}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Credits Section -->
|
||||
@@ -33,11 +36,16 @@
|
||||
v-if="authStore.isFetchingBalance"
|
||||
width="4rem"
|
||||
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
|
||||
}}</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
|
||||
:label="$t('subscription.addCredits')"
|
||||
severity="secondary"
|
||||
@@ -58,24 +66,6 @@
|
||||
/>
|
||||
</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" />
|
||||
|
||||
<div
|
||||
@@ -91,14 +81,31 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isActiveSubscription"
|
||||
class="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover"
|
||||
data-testid="plan-credits-menu-item"
|
||||
@click="handleOpenPlanAndCreditsSettings"
|
||||
data-testid="plans-pricing-menu-item"
|
||||
@click="handleOpenPlansAndPricing"
|
||||
>
|
||||
<i class="icon-[lucide--receipt-text] text-muted-foreground text-sm" />
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +116,7 @@
|
||||
>
|
||||
<i class="icon-[lucide--settings-2] text-muted-foreground text-sm" />
|
||||
<span class="text-sm text-base-foreground flex-1">{{
|
||||
$t('userSettings.title')
|
||||
$t('userSettings.accountSettings')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
@@ -143,6 +150,7 @@ import { useExternalLink } from '@/composables/useExternalLink'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
@@ -154,17 +162,18 @@ const emit = defineEmits<{
|
||||
|
||||
const { buildDocsUrl } = useExternalLink()
|
||||
|
||||
const planSettingsLabel = isCloud
|
||||
? 'settingsCategories.PlanCredits'
|
||||
: 'settingsCategories.Credits'
|
||||
|
||||
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
|
||||
useCurrentUser()
|
||||
const authActions = useFirebaseAuthActions()
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const dialogService = useDialogService()
|
||||
const { isActiveSubscription, subscriptionTierName, fetchStatus } =
|
||||
useSubscription()
|
||||
const {
|
||||
isActiveSubscription,
|
||||
subscriptionTierName,
|
||||
subscriptionTier,
|
||||
fetchStatus
|
||||
} = useSubscription()
|
||||
const subscriptionDialog = useSubscriptionDialog()
|
||||
const { flags } = useFeatureFlags()
|
||||
const { locale } = useI18n()
|
||||
|
||||
@@ -181,11 +190,21 @@ const formattedBalance = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const canUpgrade = computed(() => {
|
||||
const tier = subscriptionTier.value
|
||||
return tier === 'STANDARD' || tier === 'CREATOR'
|
||||
})
|
||||
|
||||
const handleOpenUserSettings = () => {
|
||||
dialogService.showSettingsDialog('user')
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleOpenPlansAndPricing = () => {
|
||||
subscriptionDialog.show()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleOpenPlanAndCreditsSettings = () => {
|
||||
if (isCloud) {
|
||||
dialogService.showSettingsDialog('subscription')
|
||||
|
||||
@@ -1976,6 +1976,9 @@
|
||||
"contactUs": "Contact us",
|
||||
"viewEnterprise": "View enterprise",
|
||||
"partnerNodesCredits": "Partner nodes pricing",
|
||||
"plansAndPricing": "Plans & pricing",
|
||||
"managePlan": "Manage plan",
|
||||
"upgrade": "UPGRADE",
|
||||
"mostPopular": "Most popular",
|
||||
"currentPlan": "Current Plan",
|
||||
"subscribeTo": "Subscribe to {plan}",
|
||||
@@ -2000,6 +2003,7 @@
|
||||
},
|
||||
"userSettings": {
|
||||
"title": "My Account Settings",
|
||||
"accountSettings": "Account settings",
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"provider": "Sign-in Provider",
|
||||
|
||||
Reference in New Issue
Block a user