feat: add ever-present upgrade button for free-tier users (#9315)

## Summary

Add persistent upgrade CTAs for free-tier users: a topbar button and
"Upgrade to add credits" replacing "Add Credits" in popovers and
settings panels.

## Changes

- **What**:
- New `TopbarSubscribeButton` component in both GraphCanvas and
LinearView topbars, visible only to free-tier users
- Profile popover (legacy + workspace): free-tier users see "Upgrade to
add credits" instead of "Add Credits", linking directly to the pricing
table
- Manage Plan settings (legacy + workspace): same replacement —
free-tier users see "Upgrade to add credits" instead of "Add Credits"
- Paid-tier users retain the original "Add Credits" behavior in all
locations
  - All upgrade buttons go directly to the pricing table (one-step flow)

## Review Focus

- The `isFreeTier` conditional gating on the buttons — ensure free-tier
users see upgrade CTAs and paid users see normal Add Credits
- Layout in Manage Plan panels uses `flex flex-col gap-3` to stack the
upgrade button below the usage history link instead of side-by-side

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9315-feat-add-ever-present-upgrade-button-for-free-tier-users-3166d73d365081228cdfe6a67fec6aec)
by [Unito](https://www.unito.io)
This commit is contained in:
Hunter
2026-02-28 23:07:12 -05:00
committed by GitHub
parent 7c8a548798
commit 589f58f916
12 changed files with 118 additions and 30 deletions

View File

@@ -2,15 +2,7 @@
<Button
:size
:disabled="disabled"
variant="primary"
:style="
variant === 'gradient'
? {
background: 'var(--color-subscription-button-gradient)',
color: 'var(--color-white)'
}
: undefined
"
:variant="buttonVariant === 'gradient' ? 'gradient' : 'primary'"
:class="cn('font-bold', fluid && 'w-full')"
@click="handleSubscribe"
>
@@ -31,13 +23,13 @@ import { cn } from '@/utils/tailwindUtil'
const {
size = 'lg',
fluid = true,
variant = 'default',
buttonVariant = 'default',
label,
disabled = false
} = defineProps<{
label?: string
size?: 'sm' | 'lg'
variant?: 'default' | 'gradient'
buttonVariant?: 'default' | 'gradient'
fluid?: boolean
disabled?: boolean
}>()

View File

@@ -5,13 +5,8 @@
showDelay: 600
}"
class="subscribe-to-run-button whitespace-nowrap"
variant="primary"
variant="gradient"
size="sm"
:style="{
background: 'var(--color-subscription-button-gradient)',
color: 'var(--color-white)',
borderColor: 'transparent'
}"
data-testid="subscribe-to-run-button"
@click="handleSubscribeToRun"
>

View File

@@ -130,17 +130,25 @@
</tbody>
</table>
<div class="flex items-center justify-between">
<div class="flex flex-col gap-3">
<a
href="https://platform.comfy.org/profile/usage"
target="_blank"
rel="noopener noreferrer"
class="text-sm underline text-center text-muted"
class="text-sm underline text-muted"
>
{{ $t('subscription.viewUsageHistory') }}
</a>
<Button
v-if="isActiveSubscription"
v-if="isActiveSubscription && isFreeTier"
variant="gradient"
class="p-2 min-h-8 rounded-lg text-sm font-normal w-full"
@click="handleUpgradeToAddCredits"
>
{{ $t('subscription.upgradeToAddCredits') }}
</Button>
<Button
v-else-if="isActiveSubscription"
variant="secondary"
class="p-2 min-h-8 rounded-lg text-sm font-normal text-text-primary bg-interface-menu-component-surface-selected"
@click="handleAddApiCredits"
@@ -234,7 +242,8 @@ const {
isYearlySubscription
} = useSubscription()
const { show: showSubscriptionDialog } = useSubscriptionDialog()
const { show: showSubscriptionDialog, showPricingTable } =
useSubscriptionDialog()
const tierKey = computed(() => {
const tier = subscriptionTier.value
@@ -296,6 +305,10 @@ const { totalCredits, monthlyBonusCredits, prepaidCredits, isLoadingBalance } =
const { handleAddApiCredits, handleRefresh } = useSubscriptionActions()
function handleUpgradeToAddCredits() {
showPricingTable()
}
// Focus-based polling: refresh balance when user returns from Stripe checkout
const PENDING_TOPUP_KEY = 'pending_topup_timestamp'
const TOPUP_EXPIRY_MS = 5 * 60 * 1000 // 5 minutes