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

@@ -23,6 +23,15 @@
<i class="pi pi-times text-xl" />
</Button>
<div v-if="reason === 'out_of_credits'" class="text-center">
<h2 class="text-xl lg:text-2xl text-muted-foreground m-0">
{{ $t('credits.topUp.insufficientTitle') }}
</h2>
<p class="m-0 mt-2 text-sm text-text-secondary">
{{ $t('credits.topUp.insufficientMessage') }}
</p>
</div>
<!-- Pricing Table Step -->
<PricingTableWorkspace
v-if="checkoutStep === 'pricing'"
@@ -75,16 +84,18 @@ import type { BillingCycle } from '@/platform/cloud/subscription/utils/subscript
import type { PreviewSubscribeResponse } from '@/platform/workspace/api/workspaceApi'
import { workspaceApi } from '@/platform/workspace/api/workspaceApi'
import { useBillingOperationStore } from '@/platform/workspace/stores/billingOperationStore'
import type { SubscriptionDialogReason } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
import PricingTableWorkspace from './PricingTableWorkspace.vue'
import SubscriptionAddPaymentPreviewWorkspace from './SubscriptionAddPaymentPreviewWorkspace.vue'
import SubscriptionTransitionPreviewWorkspace from './SubscriptionTransitionPreviewWorkspace.vue'
type CheckoutStep = 'pricing' | 'preview'
type CheckoutTierKey = Exclude<TierKey, 'founder'>
type CheckoutTierKey = Exclude<TierKey, 'free' | 'founder'>
const props = defineProps<{
const { onClose, reason } = defineProps<{
onClose: () => void
reason?: SubscriptionDialogReason
}>()
const emit = defineEmits<{
@@ -314,7 +325,7 @@ async function handleResubscribe() {
}
function handleClose() {
props.onClose()
onClose()
}
</script>