mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-21 23:09:39 +00:00
[backport cloud/1.34] style: redesign TopUpCredits dialog (#7313)
Backport of #7305 to `cloud/1.34` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7313-backport-cloud-1-34-style-redesign-TopUpCredits-dialog-2c56d73d36508172b633f5602a8b967d) by [Unito](https://www.unito.io) Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
@@ -7,7 +7,12 @@
|
|||||||
<Skeleton width="8rem" height="2rem" />
|
<Skeleton width="8rem" height="2rem" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex items-center gap-1">
|
<div v-else class="flex items-center gap-1">
|
||||||
<Tag severity="secondary" rounded class="p-1 text-amber-400">
|
<Tag
|
||||||
|
v-if="!showCreditsOnly"
|
||||||
|
severity="secondary"
|
||||||
|
rounded
|
||||||
|
class="p-1 text-amber-400"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i
|
<i
|
||||||
:class="
|
:class="
|
||||||
@@ -18,7 +23,9 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Tag>
|
</Tag>
|
||||||
<div :class="textClass">{{ formattedBalance }}</div>
|
<div :class="textClass">
|
||||||
|
{{ showCreditsOnly ? formattedCreditsOnly : formattedBalance }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -32,8 +39,9 @@ import { formatCreditsFromCents } from '@/base/credits/comfyCredits'
|
|||||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||||
|
|
||||||
const { textClass } = defineProps<{
|
const { textClass, showCreditsOnly } = defineProps<{
|
||||||
textClass?: string
|
textClass?: string
|
||||||
|
showCreditsOnly?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const authStore = useFirebaseAuthStore()
|
const authStore = useFirebaseAuthStore()
|
||||||
@@ -50,4 +58,14 @@ const formattedBalance = computed(() => {
|
|||||||
})
|
})
|
||||||
return `${amount} ${t('credits.credits')}`
|
return `${amount} ${t('credits.credits')}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const formattedCreditsOnly = computed(() => {
|
||||||
|
// Backend returns cents despite the *_micros naming convention.
|
||||||
|
const cents = authStore.balance?.amount_micros ?? 0
|
||||||
|
const amount = formatCreditsFromCents({
|
||||||
|
cents,
|
||||||
|
locale: locale.value
|
||||||
|
})
|
||||||
|
return amount
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,35 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- New Credits Design (default) -->
|
<!-- New Credits Design (default) -->
|
||||||
<div
|
<div v-if="useNewDesign" class="flex w-112 flex-col gap-8 p-8">
|
||||||
v-if="useNewDesign"
|
|
||||||
class="flex w-96 flex-col gap-8 p-8 bg-node-component-surface rounded-2xl border border-border-primary"
|
|
||||||
>
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<h1 class="text-2xl font-semibold text-foreground-primary m-0">
|
<h1 class="text-2xl font-semibold text-white m-0">
|
||||||
{{ $t('credits.topUp.addMoreCredits') }}
|
{{
|
||||||
|
isInsufficientCredits
|
||||||
|
? $t('credits.topUp.addMoreCreditsToRun')
|
||||||
|
: $t('credits.topUp.addMoreCredits')
|
||||||
|
}}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-sm text-foreground-secondary m-0">
|
<div v-if="isInsufficientCredits" class="flex flex-col gap-2">
|
||||||
{{ $t('credits.topUp.creditsDescription') }}
|
<p class="text-sm text-muted-foreground m-0 w-96">
|
||||||
</p>
|
{{ $t('credits.topUp.insufficientWorkflowMessage') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm text-muted-foreground m-0">
|
||||||
|
{{ $t('credits.topUp.creditsDescription') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Current Balance Section -->
|
<!-- Current Balance Section -->
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex items-baseline gap-2">
|
<div class="flex items-baseline gap-2">
|
||||||
<UserCredit text-class="text-3xl font-bold" />
|
<UserCredit text-class="text-3xl font-bold" show-credits-only />
|
||||||
<span class="text-sm text-foreground-secondary">{{
|
<span class="text-sm text-muted-foreground">{{
|
||||||
$t('credits.creditsAvailable')
|
$t('credits.creditsAvailable')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="refreshDate" class="text-sm text-foreground-secondary">
|
<div v-if="formattedRenewalDate" class="text-sm text-muted-foreground">
|
||||||
{{ $t('credits.refreshes', { date: refreshDate }) }}
|
{{ $t('credits.refreshes', { date: formattedRenewalDate }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Credit Options Section -->
|
<!-- Credit Options Section -->
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<span class="text-sm text-foreground-secondary">
|
<span class="text-sm text-muted-foreground">
|
||||||
{{ $t('credits.topUp.howManyCredits') }}
|
{{ $t('credits.topUp.howManyCredits') }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@@ -42,7 +50,7 @@
|
|||||||
@select="selectedCredits = option.credits"
|
@select="selectedCredits = option.credits"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-foreground-secondary">
|
<div class="text-xs text-muted-foreground w-96">
|
||||||
{{ $t('credits.topUp.templateNote') }}
|
{{ $t('credits.topUp.templateNote') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,7 +61,8 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
severity="primary"
|
severity="primary"
|
||||||
:label="$t('credits.topUp.buy')"
|
:label="$t('credits.topUp.buy')"
|
||||||
class="w-full"
|
:class="['w-full', { 'opacity-30': !selectedCredits || loading }]"
|
||||||
|
:pt="{ label: { class: 'text-white' } }"
|
||||||
@click="handleBuy"
|
@click="handleBuy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,6 +130,7 @@ import {
|
|||||||
import UserCredit from '@/components/common/UserCredit.vue'
|
import UserCredit from '@/components/common/UserCredit.vue'
|
||||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||||
|
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||||
import { useTelemetry } from '@/platform/telemetry'
|
import { useTelemetry } from '@/platform/telemetry'
|
||||||
|
|
||||||
import CreditTopUpOption from './credit/CreditTopUpOption.vue'
|
import CreditTopUpOption from './credit/CreditTopUpOption.vue'
|
||||||
@@ -132,18 +142,17 @@ interface CreditOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
refreshDate,
|
|
||||||
isInsufficientCredits = false,
|
isInsufficientCredits = false,
|
||||||
amountOptions = [5, 10, 20, 50],
|
amountOptions = [5, 10, 20, 50],
|
||||||
preselectedAmountOption = 10
|
preselectedAmountOption = 10
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
refreshDate?: string
|
|
||||||
isInsufficientCredits?: boolean
|
isInsufficientCredits?: boolean
|
||||||
amountOptions?: number[]
|
amountOptions?: number[]
|
||||||
preselectedAmountOption?: number
|
preselectedAmountOption?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { flags } = useFeatureFlags()
|
const { flags } = useFeatureFlags()
|
||||||
|
const { formattedRenewalDate } = useSubscription()
|
||||||
// Use feature flag to determine design - defaults to true (new design)
|
// Use feature flag to determine design - defaults to true (new design)
|
||||||
const useNewDesign = computed(() => flags.subscriptionTiersEnabled)
|
const useNewDesign = computed(() => flags.subscriptionTiersEnabled)
|
||||||
|
|
||||||
@@ -157,20 +166,20 @@ const loading = ref(false)
|
|||||||
|
|
||||||
const creditOptions: CreditOption[] = [
|
const creditOptions: CreditOption[] = [
|
||||||
{
|
{
|
||||||
credits: 1000,
|
credits: 1055, // $5.00
|
||||||
description: t('credits.topUp.videosEstimate', { count: 100 })
|
description: t('credits.topUp.videosEstimate', { count: 41 })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
credits: 5000,
|
credits: 2110, // $10.00
|
||||||
description: t('credits.topUp.videosEstimate', { count: 500 })
|
description: t('credits.topUp.videosEstimate', { count: 82 })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
credits: 10000,
|
credits: 4220, // $20.00
|
||||||
description: t('credits.topUp.videosEstimate', { count: 1000 })
|
description: t('credits.topUp.videosEstimate', { count: 184 })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
credits: 20000,
|
credits: 10550, // $50.00
|
||||||
description: t('credits.topUp.videosEstimate', { count: 2000 })
|
description: t('credits.topUp.videosEstimate', { count: 412 })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,17 @@
|
|||||||
class="flex items-center justify-between p-2 rounded-lg cursor-pointer transition-all duration-200"
|
class="flex items-center justify-between p-2 rounded-lg cursor-pointer transition-all duration-200"
|
||||||
:class="[
|
:class="[
|
||||||
selected
|
selected
|
||||||
? 'bg-surface-secondary border-2 border-primary'
|
? 'bg-secondary-background border-2 border-border-default'
|
||||||
: 'bg-surface-tertiary border border-border-primary hover:bg-surface-secondary'
|
: 'bg-component-node-disabled hover:bg-secondary-background border-2 border-transparent'
|
||||||
]"
|
]"
|
||||||
@click="$emit('select')"
|
@click="$emit('select')"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col">
|
<span class="text-base font-bold text-white">
|
||||||
<span class="text-base font-medium text-foreground-primary">
|
{{ formattedCredits }}
|
||||||
{{ formattedCredits }}
|
</span>
|
||||||
</span>
|
<span class="text-sm font-normal text-white">
|
||||||
<span class="text-sm text-foreground-secondary">
|
{{ description }}
|
||||||
{{ description }}
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -38,6 +36,10 @@ defineEmits<{
|
|||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
|
|
||||||
const formattedCredits = computed(() => {
|
const formattedCredits = computed(() => {
|
||||||
return formatCredits({ value: credits, locale: locale.value })
|
return formatCredits({
|
||||||
|
value: credits,
|
||||||
|
locale: locale.value,
|
||||||
|
numberOptions: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1836,6 +1836,8 @@
|
|||||||
"seeDetails": "See details",
|
"seeDetails": "See details",
|
||||||
"topUp": "Top Up",
|
"topUp": "Top Up",
|
||||||
"addMoreCredits": "Add more credits",
|
"addMoreCredits": "Add more credits",
|
||||||
|
"addMoreCreditsToRun": "Add more credits to run",
|
||||||
|
"insufficientWorkflowMessage": "You don't have enough credits to run this workflow.",
|
||||||
"creditsDescription": "Credits are used to run workflows or partner nodes.",
|
"creditsDescription": "Credits are used to run workflows or partner nodes.",
|
||||||
"howManyCredits": "How many credits would you like to add?",
|
"howManyCredits": "How many credits would you like to add?",
|
||||||
"videosEstimate": "~{count} videos*",
|
"videosEstimate": "~{count} videos*",
|
||||||
@@ -1858,7 +1860,7 @@
|
|||||||
"accountInitialized": "Account initialized",
|
"accountInitialized": "Account initialized",
|
||||||
"unified": {
|
"unified": {
|
||||||
"message": "Credits have been unified",
|
"message": "Credits have been unified",
|
||||||
"tooltip": "We've unified payments across Comfy. Everything now runs on Comfy Credits:\n- Partner Nodes (formerly API nodes)\n- Cloud workflows\n\nYour existing Partner node balance has been converted into credits.\nLearn more here."
|
"tooltip": "We've unified payments across Comfy. Everything now runs on Comfy Credits:\n- Partner Nodes (formerly API nodes)\n- Cloud workflows\n\nYour existing Partner node balance has been converted into credits."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
|
|||||||
@@ -386,11 +386,12 @@ export const useDialogService = () => {
|
|||||||
return dialogStore.showDialog({
|
return dialogStore.showDialog({
|
||||||
key: 'top-up-credits',
|
key: 'top-up-credits',
|
||||||
component: TopUpCreditsDialogContent,
|
component: TopUpCreditsDialogContent,
|
||||||
headerComponent: ComfyOrgHeader,
|
|
||||||
props: options,
|
props: options,
|
||||||
dialogComponentProps: {
|
dialogComponentProps: {
|
||||||
|
headless: true,
|
||||||
pt: {
|
pt: {
|
||||||
header: { class: 'p-3!' }
|
header: { class: 'p-0! hidden' },
|
||||||
|
content: { class: 'p-0! m-0!' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,16 +32,12 @@ describe('CreditTopUpOption', () => {
|
|||||||
expect(wrapper.text()).toContain('~500 videos*')
|
expect(wrapper.text()).toContain('~500 videos*')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('applies selected styling when selected', () => {
|
|
||||||
const wrapper = mountOption({ selected: true })
|
|
||||||
expect(wrapper.find('div').classes()).toContain('bg-surface-secondary')
|
|
||||||
expect(wrapper.find('div').classes()).toContain('border-primary')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('applies unselected styling when not selected', () => {
|
it('applies unselected styling when not selected', () => {
|
||||||
const wrapper = mountOption({ selected: false })
|
const wrapper = mountOption({ selected: false })
|
||||||
expect(wrapper.find('div').classes()).toContain('bg-surface-tertiary')
|
expect(wrapper.find('div').classes()).toContain(
|
||||||
expect(wrapper.find('div').classes()).toContain('border-border-primary')
|
'bg-component-node-disabled'
|
||||||
|
)
|
||||||
|
expect(wrapper.find('div').classes()).toContain('border-transparent')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits select event when clicked', async () => {
|
it('emits select event when clicked', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user