diff --git a/frontend/src/components/common/PlatformTypeBadge.vue b/frontend/src/components/common/PlatformTypeBadge.vue index 1e664d51bc..00c61b25fa 100644 --- a/frontend/src/components/common/PlatformTypeBadge.vue +++ b/frontend/src/components/common/PlatformTypeBadge.vue @@ -139,6 +139,8 @@ const planLabel = computed(() => { return 'SuperGrok' case 'supergrokheavy': return 'SuperGrok Heavy' + case 'heavy': + return 'Heavy' case 'abnormal': return t('admin.accounts.subscriptionAbnormal') default: @@ -153,9 +155,12 @@ const isGrokFreePlan = computed(() => const planIconName = computed<'bolt' | null>(() => { if (props.platform !== 'grok') return null + // Paid Grok tiers (SuperGrok / Heavy) share the bolt mark; free uses GrokFreeIcon. if ( normalizedPlanType.value === 'supergrok' || - normalizedPlanType.value === 'supergrokheavy' + normalizedPlanType.value === 'supergrokheavy' || + normalizedPlanType.value === 'heavy' || + normalizedPlanType.value.includes('heavy') ) { return 'bolt' } @@ -198,6 +203,32 @@ const planBadgeClass = computed(() => { if (normalizedPlanType.value === 'abnormal') { return 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' } + // Free stays muted gray; paid Grok tiers get distinct colors (aligned with personal-dev). + if (normalizedPlanType.value === 'free' || normalizedPlanType.value === 'basic') { + return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300' + } + if (props.platform === 'grok' && normalizedPlanType.value) { + // Heavy / SuperGrok Heavy → purple + if (normalizedPlanType.value.includes('heavy')) { + return 'bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-300' + } + // SuperGrok → cyan + if (normalizedPlanType.value.includes('supergrok')) { + return 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-300' + } + // Any other non-free Grok plan (future tiers) → amber so it still stands out + return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300' + } + // OpenAI / other paid plan labels: keep readable distinction from free gray + if (normalizedPlanType.value === 'plus') { + return 'bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-300' + } + if (normalizedPlanType.value === 'team') { + return 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300' + } + if (normalizedPlanType.value === 'pro' || normalizedPlanType.value === 'chatgptpro') { + return 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300' + } return typeClass.value }) diff --git a/frontend/src/components/common/__tests__/PlatformTypeBadge.grok.spec.ts b/frontend/src/components/common/__tests__/PlatformTypeBadge.grok.spec.ts index 0bad3d9551..9dc8993645 100644 --- a/frontend/src/components/common/__tests__/PlatformTypeBadge.grok.spec.ts +++ b/frontend/src/components/common/__tests__/PlatformTypeBadge.grok.spec.ts @@ -46,6 +46,8 @@ describe('PlatformTypeBadge Grok plans', () => { expect(wrapper.text()).toContain('SuperGrok Heavy') expect(wrapper.find('[data-testid="grok-plan-icon"]').exists()).toBe(true) expect(wrapper.find('[data-testid="grok-free-plan-icon"]').exists()).toBe(false) + // Heavy uses purple plan chip (personal-dev parity) + expect(wrapper.html()).toContain('bg-purple-100') await wrapper.setProps({ platform: 'openai', planType: 'free' }) expect(wrapper.text()).toContain('Free') @@ -53,6 +55,29 @@ describe('PlatformTypeBadge Grok plans', () => { expect(wrapper.find('[data-testid="grok-plan-icon"]').exists()).toBe(false) }) + it('colors free gray, SuperGrok cyan, and Heavy purple', async () => { + const free = mount(PlatformTypeBadge, { + props: { platform: 'grok', type: 'oauth', planType: 'free' }, + }) + expect(free.html()).toContain('bg-gray-100') + expect(free.html()).not.toContain('bg-purple-100') + expect(free.html()).not.toContain('bg-cyan-100') + + const superGrok = mount(PlatformTypeBadge, { + props: { platform: 'grok', type: 'oauth', planType: 'supergrok' }, + }) + expect(superGrok.text()).toContain('SuperGrok') + expect(superGrok.html()).toContain('bg-cyan-100') + expect(superGrok.find('[data-testid="grok-plan-icon"]').exists()).toBe(true) + + const heavy = mount(PlatformTypeBadge, { + props: { platform: 'grok', type: 'oauth', planType: 'Heavy' }, + }) + expect(heavy.text()).toContain('Heavy') + expect(heavy.html()).toContain('bg-purple-100') + expect(heavy.find('[data-testid="grok-plan-icon"]').exists()).toBe(true) + }) + it('uses a dedicated 12px currentColor Grok mark with a Free sparkle', () => { const wrapper = mount(GrokFreeIcon)