From a78e8c587f7b2c44938ef7588f72f567edc9bd8e Mon Sep 17 00:00:00 2001 From: Simula_r <18093452+simula-r@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:06:14 -0800 Subject: [PATCH] Refactor(cloud)/yearly credits monthly (#7584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Add yearly total credits vs monthly. Also pulled out numerical values from the main.json to avoid translation issues and used n() for better currency support on prices. ## Changes - **What**: PricingTable.vue, /en/main.json - **Breaking**: - **Dependencies**: ## Review Focus ## Screenshots (if applicable) image ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7584-Refactor-cloud-yearly-credits-monthly-2cc6d73d365081b28afbec7f9d22546f) by [Unito](https://www.unito.io) --- src/locales/en/main.json | 74 ++----------------- .../subscription/components/PricingTable.vue | 69 +++++++++-------- 2 files changed, 44 insertions(+), 99 deletions(-) diff --git a/src/locales/en/main.json b/src/locales/en/main.json index fe15168c5..e81191442 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -1938,7 +1938,7 @@ "viewMoreDetails": "View more details", "learnMore": "Learn more", "billedMonthly": "Billed monthly", - "billedAnnually": "{total} Billed annually", + "billedYearly": "{total} Billed yearly", "monthly": "Monthly", "yearly": "Yearly", "messageSupport": "Message support", @@ -1950,72 +1950,16 @@ "yearlyDiscount": "20% DISCOUNT", "tiers": { "founder": { - "name": "Founder's Edition", - "price": "20", - "benefits": { - "monthlyCredits": "5,460", - "monthlyCreditsLabel": "monthly credits", - "maxDuration": "30 min", - "maxDurationLabel": "max duration of each workflow run", - "gpuLabel": "RTX 6000 Pro (96GB VRAM)", - "addCreditsLabel": "Add more credits whenever", - "customLoRAsLabel": "Import your own LoRAs" - } + "name": "Founder's Edition" }, "standard": { - "name": "Standard", - "price": { - "monthly": "20", - "yearly": "16", - "annualTotal": "$192" - }, - "benefits": { - "monthlyCredits": "4,200", - "monthlyCreditsLabel": "monthly credits", - "maxDuration": "30 min", - "maxDurationLabel": "max duration of each workflow run", - "gpuLabel": "RTX 6000 Pro (96GB VRAM)", - "addCreditsLabel": "Add more credits whenever", - "customLoRAsLabel": "Import your own LoRAs", - "videoEstimate": "120" - } + "name": "Standard" }, "creator": { - "name": "Creator", - "price": { - "monthly": "35", - "yearly": "28", - "annualTotal": "$336" - }, - - "benefits": { - "monthlyCredits": "7,400", - "monthlyCreditsLabel": "monthly credits", - "maxDuration": "30 min", - "maxDurationLabel": "max duration of each workflow run", - "gpuLabel": "RTX 6000 Pro (96GB VRAM)", - "addCreditsLabel": "Add more credits whenever", - "customLoRAsLabel": "Import your own LoRAs", - "videoEstimate": "288" - } + "name": "Creator" }, "pro": { - "name": "Pro", - "price": { - "monthly": "100", - "yearly": "80", - "annualTotal": "$960" - }, - "benefits": { - "monthlyCredits": "21,100", - "monthlyCreditsLabel": "monthly credits", - "maxDuration": "1 hr", - "maxDurationLabel": "max duration of each workflow run", - "gpuLabel": "RTX 6000 Pro (96GB VRAM)", - "addCreditsLabel": "Add more credits whenever", - "customLoRAsLabel": "Import your own LoRAs", - "videoEstimate": "815" - } + "name": "Pro" } }, "required": { @@ -2036,6 +1980,7 @@ "currentPlan": "Current Plan", "subscribeTo": "Subscribe to {plan}", "monthlyCreditsLabel": "Monthly credits", + "yearlyCreditsLabel": "Total yearly credits", "maxDurationLabel": "Max duration of each workflow run", "gpuLabel": "RTX 6000 Pro (96GB VRAM)", "addCreditsLabel": "Add more credits whenever", @@ -2047,11 +1992,6 @@ "upgradePlan": "Upgrade Plan", "upgradeTo": "Upgrade to {plan}", "changeTo": "Change to {plan}", - "credits": { - "standard": "4,200", - "creator": "7,400", - "pro": "21,100" - }, "maxDuration": { "standard": "30 min", "creator": "30 min", @@ -2478,4 +2418,4 @@ "recentReleases": "Recent releases", "helpCenterMenu": "Help Center Menu" } -} \ No newline at end of file +} diff --git a/src/platform/cloud/subscription/components/PricingTable.vue b/src/platform/cloud/subscription/components/PricingTable.vue index 47c1ac66a..52f8c3571 100644 --- a/src/platform/cloud/subscription/components/PricingTable.vue +++ b/src/platform/cloud/subscription/components/PricingTable.vue @@ -73,7 +73,7 @@ v-show="currentBillingCycle === 'yearly'" class="line-through text-2xl text-muted-foreground" > - ${{ tier.price.monthly }} + ${{ tier.pricing.monthly }} ${{ getPrice(tier) }} @@ -87,8 +87,8 @@ {{ currentBillingCycle === 'yearly' - ? t('subscription.billedAnnually', { - total: tier.price.annualTotal + ? t('subscription.billedYearly', { + total: `$${getAnnualTotal(tier)}` }) : t('subscription.billedMonthly') }} @@ -102,14 +102,18 @@ - {{ t('subscription.monthlyCreditsLabel') }} + {{ + currentBillingCycle === 'yearly' + ? t('subscription.yearlyCreditsLabel') + : t('subscription.monthlyCreditsLabel') + }}
- {{ tier.credits }} + {{ n(getCreditsDisplay(tier)) }}
@@ -171,7 +175,7 @@ - {{ tier.videoEstimate }} + {{ n(tier.pricing.videoEstimate) }} @@ -242,6 +246,7 @@ import Popover from 'primevue/popover' import SelectButton from 'primevue/selectbutton' import type { ToggleButtonPassThroughMethodOptions } from 'primevue/togglebutton' import { computed, ref } from 'vue' +import { useI18n } from 'vue-i18n' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useErrorHandling } from '@/composables/useErrorHandling' @@ -271,15 +276,26 @@ interface BillingCycleOption { value: BillingCycle } +interface TierPricing { + monthly: number + yearly: number + credits: number + videoEstimate: number +} + +const TIER_PRICING: Record = { + standard: { monthly: 20, yearly: 16, credits: 4200, videoEstimate: 164 }, + creator: { monthly: 35, yearly: 28, credits: 7400, videoEstimate: 288 }, + pro: { monthly: 100, yearly: 80, credits: 21100, videoEstimate: 821 } +} as const + interface PricingTierConfig { id: SubscriptionTier key: TierKey name: string - price: Record & { annualTotal: string } - credits: string + pricing: TierPricing maxDuration: string customLoRAs: boolean - videoEstimate: string isPopular?: boolean } @@ -300,49 +316,32 @@ const tiers: PricingTierConfig[] = [ id: 'STANDARD', key: 'standard', name: t('subscription.tiers.standard.name'), - price: { - monthly: t('subscription.tiers.standard.price.monthly'), - yearly: t('subscription.tiers.standard.price.yearly'), - annualTotal: t('subscription.tiers.standard.price.annualTotal') - }, - credits: t('subscription.credits.standard'), + pricing: TIER_PRICING.standard, maxDuration: t('subscription.maxDuration.standard'), customLoRAs: false, - videoEstimate: t('subscription.tiers.standard.benefits.videoEstimate'), isPopular: false }, { id: 'CREATOR', key: 'creator', name: t('subscription.tiers.creator.name'), - price: { - monthly: t('subscription.tiers.creator.price.monthly'), - yearly: t('subscription.tiers.creator.price.yearly'), - annualTotal: t('subscription.tiers.creator.price.annualTotal') - }, - credits: t('subscription.credits.creator'), + pricing: TIER_PRICING.creator, maxDuration: t('subscription.maxDuration.creator'), customLoRAs: true, - videoEstimate: t('subscription.tiers.creator.benefits.videoEstimate'), isPopular: true }, { id: 'PRO', key: 'pro', name: t('subscription.tiers.pro.name'), - price: { - monthly: t('subscription.tiers.pro.price.monthly'), - yearly: t('subscription.tiers.pro.price.yearly'), - annualTotal: t('subscription.tiers.pro.price.annualTotal') - }, - credits: t('subscription.credits.pro'), + pricing: TIER_PRICING.pro, maxDuration: t('subscription.maxDuration.pro'), customLoRAs: true, - videoEstimate: t('subscription.tiers.pro.benefits.videoEstimate'), isPopular: false } ] +const { n } = useI18n() const { getAuthHeader } = useFirebaseAuthStore() const { isActiveSubscription, subscriptionTier } = useSubscription() const { accessBillingPortal, reportError } = useFirebaseAuthActions() @@ -383,8 +382,14 @@ const getButtonTextClass = (tier: PricingTierConfig): string => ? 'font-inter text-sm font-bold leading-normal text-base-background' : 'font-inter text-sm font-bold leading-normal text-primary-foreground' -const getPrice = (tier: PricingTierConfig): string => - tier.price[currentBillingCycle.value] +const getPrice = (tier: PricingTierConfig): number => + tier.pricing[currentBillingCycle.value] + +const getAnnualTotal = (tier: PricingTierConfig): number => + tier.pricing.yearly * 12 + +const getCreditsDisplay = (tier: PricingTierConfig): number => + tier.pricing.credits * (currentBillingCycle.value === 'yearly' ? 12 : 1) const initiateCheckout = async (tierKey: TierKey) => { const authHeader = await getAuthHeader()