Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/components/topbar/CurrentUserPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
<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">
{{ subscriptionTierName }}
</p>
</div>

<!-- Credits Section -->
Expand Down Expand Up @@ -160,7 +163,8 @@ const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
const authActions = useFirebaseAuthActions()
const authStore = useFirebaseAuthStore()
const dialogService = useDialogService()
const { isActiveSubscription, fetchStatus } = useSubscription()
const { isActiveSubscription, subscriptionTierName, fetchStatus } =
useSubscription()
const { flags } = useFeatureFlags()
const { locale } = useI18n()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<div class="flex items-center justify-between">
<div class="flex flex-col gap-2">
<div class="text-sm font-bold text-text-primary">
{{ tierName }}
{{ subscriptionTierName }}
</div>
<div class="flex items-baseline gap-1 font-inter font-semibold">
<span class="text-2xl">${{ tierPrice }}</span>
Expand Down Expand Up @@ -377,6 +377,7 @@ const {
formattedRenewalDate,
formattedEndDate,
subscriptionTier,
subscriptionTierName,
handleInvoiceHistory
} = useSubscription()

Expand All @@ -387,8 +388,6 @@ const tierKey = computed(() => {
if (!tier) return DEFAULT_TIER_KEY
return TIER_TO_I18N_KEY[tier] ?? DEFAULT_TIER_KEY
})

const tierName = computed(() => t(`subscription.tiers.${tierKey.value}.name`))
const tierPrice = computed(() => t(`subscription.tiers.${tierKey.value}.price`))

// Tier benefits for v-for loop
Expand Down
19 changes: 18 additions & 1 deletion src/platform/cloud/subscription/composables/useSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
useFirebaseAuthStore
} from '@/stores/firebaseAuthStore'
import { useDialogService } from '@/services/dialogService'
import type { operations } from '@/types/comfyRegistryTypes'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { useSubscriptionCancellationWatcher } from './useSubscriptionCancellationWatcher'

type CloudSubscriptionCheckoutResponse = {
Expand All @@ -24,6 +24,15 @@ export type CloudSubscriptionStatusResponse = NonNullable<
operations['GetCloudSubscriptionStatus']['responses']['200']['content']['application/json']
>

type SubscriptionTier = components['schemas']['SubscriptionTier']

const TIER_TO_I18N_KEY: Record<SubscriptionTier, string> = {
STANDARD: 'standard',
CREATOR: 'creator',
PRO: 'pro',
FOUNDERS_EDITION: 'founder'
}

Comment on lines +27 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Typed tier→i18n mapping and subscriptionTierName computed are well‑structured

Deriving SubscriptionTier from components['schemas']['SubscriptionTier'] and using it as the key for TIER_TO_I18N_KEY keeps the mapping strongly typed against the backend schema. The subscriptionTierName computed cleanly handles the “no tier yet” case by returning an empty string and uses the i18n key space consistently, with a safe 'standard' fallback if a future tier slipped through unmapped.

One thing to watch is that a very similar TIER_TO_I18N_KEY mapping still exists in SubscriptionPanel.vue for price/benefit lookups, so tier key→i18n mapping logic now lives in two places. Not a blocker, but it may be worth centralizing (e.g., via a small shared helper or exported mapping) to avoid divergence if tiers change later.

Also applies to: 85-90, 244-245

🤖 Prompt for AI Agents
In src/platform/cloud/subscription/composables/useSubscription.ts around lines
27–35 (and also update the similar mappings at lines 85–90 and 244–245), the
tier→i18n mapping is duplicated in SubscriptionPanel.vue; extract
TIER_TO_I18N_KEY into a single exported helper (or a small shared module) and
export it from useSubscription.ts (or a new shared file), update the computed
subscriptionTierName to import and use that exported mapping, and replace the
duplicate mapping in SubscriptionPanel.vue with an import from the shared helper
so both places use the same source of truth.

function useSubscriptionInternal() {
const subscriptionStatus = ref<CloudSubscriptionStatusResponse | null>(null)
const telemetry = useTelemetry()
Expand Down Expand Up @@ -73,6 +82,13 @@ function useSubscriptionInternal() {
() => subscriptionStatus.value?.subscription_tier ?? null
)

const subscriptionTierName = computed(() => {
const tier = subscriptionTier.value
if (!tier) return ''
const key = TIER_TO_I18N_KEY[tier] ?? 'standard'
return t(`subscription.tiers.${key}.name`)
})

const buildApiUrl = (path: string) => `${getComfyApiBaseUrl()}${path}`

const fetchStatus = wrapWithErrorHandlingAsync(
Expand Down Expand Up @@ -225,6 +241,7 @@ function useSubscriptionInternal() {
formattedRenewalDate,
formattedEndDate,
subscriptionTier,
subscriptionTierName,

// Actions
subscribe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@ const mockSubscriptionTier = ref<
'STANDARD' | 'CREATOR' | 'PRO' | 'FOUNDERS_EDITION' | null
>('CREATOR')

const TIER_TO_NAME: Record<string, string> = {
STANDARD: 'Standard',
CREATOR: 'Creator',
PRO: 'Pro',
FOUNDERS_EDITION: "Founder's Edition"
}
Comment on lines +16 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Tier→name mapping and mocked subscriptionTierName align with app behavior

The TIER_TO_NAME map plus the subscriptionTierName computed keep the test double in sync with the production composable’s contract and ensure the panel renders the correct, human‑readable tier label. The simple null guard is sufficient for the current union type of mockSubscriptionTier.

If you want to tighten things a bit, you could narrow the key type (e.g., to the literal union used for mockSubscriptionTier) to catch mismatches at compile time, but that’s optional for this test helper.

Also applies to: 30-32

🤖 Prompt for AI Agents
In
tests-ui/tests/platform/cloud/subscription/components/SubscriptionPanel.test.ts
around lines 16-21 (and likewise adjust 30-32), the TIER_TO_NAME mapping is
typed as Record<string,string> which is too permissive; tighten the key type to
the actual tier union used by mockSubscriptionTier (e.g., use an explicit union
type for keys or create the object with a const assertion and derive the key
type) so mismatches are caught at compile time, and ensure the mocked
subscriptionTierName computed still uses the same null guard logic against that
narrowed type.


// Mock composables - using computed to match composable return types
const mockSubscriptionData = {
isActiveSubscription: computed(() => mockIsActiveSubscription.value),
isCancelled: computed(() => mockIsCancelled.value),
formattedRenewalDate: computed(() => '2024-12-31'),
formattedEndDate: computed(() => '2024-12-31'),
subscriptionTier: computed(() => mockSubscriptionTier.value),
subscriptionTierName: computed(() =>
mockSubscriptionTier.value ? TIER_TO_NAME[mockSubscriptionTier.value] : ''
),
handleInvoiceHistory: vi.fn()
}

Expand Down
Loading