feat: add Free subscription tier support (#8864)

## Summary

Add frontend support for a Free subscription tier — login/signup page
restructuring, telemetry instrumentation, and tier-aware billing gating.

## Changes

- **What**: 
- Restructure login/signup pages: OAuth buttons promoted as primary
sign-in method, email login available via progressive disclosure
- Add Free tier badge on Google sign-up button with dynamic credit count
from remote config
- Add `FREE` subscription tier to type system (tier pricing, tier rank,
registry types)
  - Add `isFreeTier` computed to `useSubscription()`
- Disable credit top-up for Free tier users (dialogService,
purchaseCredits, popover CTA)
- Show subscription/upgrade dialog instead of top-up dialog when Free
tier user hits out-of-credits
- Add funnel telemetry: `trackLoginOpened`, enrich `trackSignupOpened`
with `free_tier_badge_shown`, track email toggle clicks

## Review Focus

- Tier gating logic: Free tier users should see "Upgrade" instead of
"Add Credits" and never reach the top-up flow
- Telemetry event design for Mixpanel funnel analysis
- Progressive disclosure UX on login/signup pages

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8864-feat-add-Free-subscription-tier-support-3076d73d36508133b84ec5f0a67ccb03)
by [Unito](https://www.unito.io)
This commit is contained in:
Hunter
2026-02-24 23:28:51 -05:00
committed by GitHub
parent aee207f16c
commit 8c3738fb77
39 changed files with 720 additions and 221 deletions

View File

@@ -272,7 +272,7 @@ import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import type { components } from '@/types/comfyRegistryTypes'
type SubscriptionTier = components['schemas']['SubscriptionTier']
type CheckoutTierKey = Exclude<TierKey, 'founder'>
type CheckoutTierKey = Exclude<TierKey, 'free' | 'founder'>
type CheckoutTier = CheckoutTierKey | `${CheckoutTierKey}-yearly`
const getCheckoutTier = (
@@ -344,8 +344,12 @@ const tiers: PricingTierConfig[] = [
isPopular: false
}
]
const { isActiveSubscription, subscriptionTier, isYearlySubscription } =
useSubscription()
const {
isActiveSubscription,
isFreeTier,
subscriptionTier,
isYearlySubscription
} = useSubscription()
const telemetry = useTelemetry()
const { userId } = storeToRefs(useFirebaseAuthStore())
const { accessBillingPortal, reportError } = useFirebaseAuthActions()
@@ -356,6 +360,10 @@ const loadingTier = ref<CheckoutTierKey | null>(null)
const popover = ref()
const currentBillingCycle = ref<BillingCycle>('yearly')
const hasPaidSubscription = computed(
() => isActiveSubscription.value && !isFreeTier.value
)
const currentTierKey = computed<TierKey | null>(() =>
subscriptionTier.value ? TIER_TO_KEY[subscriptionTier.value] : null
)
@@ -392,7 +400,7 @@ const getButtonLabel = (tier: PricingTierConfig): string => {
? t('subscription.tierNameYearly', { name: tier.name })
: tier.name
return isActiveSubscription.value
return hasPaidSubscription.value
? t('subscription.changeTo', { plan: planName })
: t('subscription.subscribeTo', { plan: planName })
}
@@ -427,7 +435,7 @@ const handleSubscribe = wrapWithErrorHandlingAsync(
loadingTier.value = tierKey
try {
if (isActiveSubscription.value) {
if (hasPaidSubscription.value) {
const checkoutAttribution = await getCheckoutAttributionForCloud()
if (userId.value) {
telemetry?.trackBeginCheckout({