mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 14:57:36 +08:00
feat(codex): recognize current subscription plans
This commit is contained in:
@@ -121,3 +121,13 @@ func TestNormalizeOpenAIPersonalAccessTokenCredentialsRemovesOAuthFields(t *test
|
||||
require.Equal(t, "2026-12-31T00:00:00Z", got["subscription_expires_at"])
|
||||
require.Equal(t, []any{"custom"}, got["openai_usage_channel_fields"])
|
||||
}
|
||||
|
||||
func TestOpenAICodexSubscriptionSKUsSurviveCredentialBuild(t *testing.T) {
|
||||
svc := &OpenAIOAuthService{}
|
||||
for _, plan := range []string{"prolite", "pro", "promax", "ent26", "enterprise_cbp_automation", "enterprise_cbp_usage_based", "edu_plus", "edu_pro", "future_sku"} {
|
||||
for _, mode := range []string{"", OpenAIAuthModePersonalAccessToken} {
|
||||
creds := svc.BuildAccountCredentials(&OpenAITokenInfo{AccessToken: "fixture", PlanType: plan, AuthMode: mode})
|
||||
require.Equal(t, plan, creds["plan_type"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { openAIPlanTypeLabel } from '@/utils/planType'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
ANTIGRAVITY_PROJECT_ID_CREDENTIAL_KEY,
|
||||
@@ -451,17 +452,17 @@ describe('plan_type helpers', () => {
|
||||
describe('planTypeDisplayLabel', () => {
|
||||
it('maps canonical + alias values to friendly labels', () => {
|
||||
expect(planTypeDisplayLabel('plus')).toBe('Plus')
|
||||
expect(planTypeDisplayLabel('pro')).toBe('Pro 20x')
|
||||
expect(planTypeDisplayLabel('chatgptpro')).toBe('Pro 20x')
|
||||
expect(planTypeDisplayLabel('prolite')).toBe('Pro 5x')
|
||||
expect(planTypeDisplayLabel('pro')).toBe('Pro 200')
|
||||
expect(planTypeDisplayLabel('chatgptpro')).toBe('Pro 200')
|
||||
expect(planTypeDisplayLabel('prolite')).toBe('Pro 100')
|
||||
expect(planTypeDisplayLabel('free')).toBe('Free')
|
||||
expect(planTypeDisplayLabel('team')).toBe('Business Standard')
|
||||
expect(planTypeDisplayLabel('team')).toBe('Business')
|
||||
expect(planTypeDisplayLabel('self_serve_business_prolite')).toBe('Business Premium')
|
||||
})
|
||||
it('normalizes case, separators and surrounding blanks', () => {
|
||||
expect(planTypeDisplayLabel('CHATGPTPRO')).toBe('Pro 20x')
|
||||
expect(planTypeDisplayLabel('PROLITE')).toBe('Pro 5x')
|
||||
expect(planTypeDisplayLabel(' Pro Lite ')).toBe('Pro 5x')
|
||||
expect(planTypeDisplayLabel('CHATGPTPRO')).toBe('Pro 200')
|
||||
expect(planTypeDisplayLabel('PROLITE')).toBe('Pro 100')
|
||||
expect(planTypeDisplayLabel(' Pro Lite ')).toBe('Pro 100')
|
||||
expect(planTypeDisplayLabel('self-serve-business-pro-lite')).toBe('Business Premium')
|
||||
})
|
||||
it('returns unknown values verbatim', () => {
|
||||
@@ -483,73 +484,37 @@ describe('plan_type helpers', () => {
|
||||
})
|
||||
|
||||
describe('buildPlanTypeOptions', () => {
|
||||
const clear = 'Clear'
|
||||
it('returns clear + presets when current is empty', () => {
|
||||
expect(buildPlanTypeOptions('', clear)).toEqual([
|
||||
{ value: '', label: clear },
|
||||
{ value: 'plus', label: 'Plus' },
|
||||
{ value: 'pro', label: 'Pro 20x' },
|
||||
{ value: 'prolite', label: 'Pro 5x' },
|
||||
{ value: 'self_serve_business_prolite', label: 'Business Premium' },
|
||||
{ value: 'free', label: 'Free' }
|
||||
])
|
||||
it('includes all current SKUs with their status labels', () => {
|
||||
const opts = buildPlanTypeOptions('', 'Clear')
|
||||
expect(opts[0]).toEqual({ value: '', label: 'Clear' })
|
||||
expect(opts).toContainEqual({ value: 'prolite', label: 'Pro 100' })
|
||||
expect(opts).toContainEqual({ value: 'pro', label: 'Pro 200' })
|
||||
expect(opts).toContainEqual({ value: 'promax', label: 'Pro 500' })
|
||||
expect(opts).toContainEqual({ value: 'edu_plus', label: 'Edu Plus' })
|
||||
expect(opts).toContainEqual({ value: 'edu_pro', label: 'Edu Pro' })
|
||||
expect(opts).toContainEqual({ value: 'enterprise_cbp_automation', label: 'Enterprise (Automation)' })
|
||||
})
|
||||
it('keeps canonical chatgptpro under a single friendly "Pro 20x" option (no duplicate)', () => {
|
||||
const opts = buildPlanTypeOptions('chatgptpro', clear)
|
||||
const pros = opts.filter(o => o.label === 'Pro 20x')
|
||||
expect(pros).toHaveLength(1)
|
||||
expect(pros[0].value).toBe('chatgptpro')
|
||||
expect(opts.map(o => o.value)).toEqual([
|
||||
'',
|
||||
'plus',
|
||||
'chatgptpro',
|
||||
'prolite',
|
||||
'self_serve_business_prolite',
|
||||
'free'
|
||||
])
|
||||
})
|
||||
it('appends an unknown-but-labeled value (team) as its own option', () => {
|
||||
const opts = buildPlanTypeOptions('team', clear)
|
||||
expect(opts.find(o => o.value === 'team')).toEqual({ value: 'team', label: 'Business Standard' })
|
||||
// presets untouched
|
||||
expect(opts.map(o => o.value)).toEqual([
|
||||
'',
|
||||
'plus',
|
||||
'pro',
|
||||
'prolite',
|
||||
'self_serve_business_prolite',
|
||||
'free',
|
||||
'team'
|
||||
])
|
||||
})
|
||||
it('appends a fully custom value with a raw label', () => {
|
||||
const opts = buildPlanTypeOptions('weird_x', clear)
|
||||
expect(opts.at(-1)).toEqual({ value: 'weird_x', label: 'weird_x' })
|
||||
})
|
||||
it('does not duplicate an exact preset value', () => {
|
||||
const opts = buildPlanTypeOptions('pro', clear)
|
||||
expect(opts.filter(o => o.value === 'pro')).toHaveLength(1)
|
||||
expect(opts.map(o => o.value)).toEqual([
|
||||
'',
|
||||
'plus',
|
||||
'pro',
|
||||
'prolite',
|
||||
'self_serve_business_prolite',
|
||||
'free'
|
||||
])
|
||||
})
|
||||
it('does not duplicate a preset value that is the current one', () => {
|
||||
for (const preset of ['prolite', 'self_serve_business_prolite']) {
|
||||
const opts = buildPlanTypeOptions(preset, clear)
|
||||
expect(opts.filter(o => o.value === preset)).toHaveLength(1)
|
||||
it('preserves aliases without duplicating the same SKU', () => {
|
||||
for (const [alias, canonical] of [['chatgpt_pro', 'pro'], ['pro_lite', 'prolite'], ['selfservebusinessprolite', 'self_serve_business_prolite']]) {
|
||||
const opts = buildPlanTypeOptions(alias, 'Clear')
|
||||
expect(opts.filter(option => option.value === alias)).toHaveLength(1)
|
||||
expect(opts.some(option => option.value === canonical)).toBe(false)
|
||||
}
|
||||
})
|
||||
it('keeps a separator-free variant of a preset as its own value', () => {
|
||||
const opts = buildPlanTypeOptions('selfservebusinessprolite', clear)
|
||||
expect(opts.find(o => o.value === 'selfservebusinessprolite')).toEqual({
|
||||
value: 'selfservebusinessprolite',
|
||||
label: 'Business Premium'
|
||||
})
|
||||
it('keeps distinct SKUs that share the Enterprise label', () => {
|
||||
const opts = buildPlanTypeOptions('ent26', 'Clear')
|
||||
expect(opts.filter(option => option.label === 'Enterprise').map(option => option.value)).toEqual([
|
||||
'business', 'enterprise', 'ent26', 'enterprise_cbp_usage_based'
|
||||
])
|
||||
for (const value of ['business', 'enterprise', 'ent26', 'enterprise_cbp_usage_based']) {
|
||||
expect(applyPlanType({}, value)).toEqual({ plan_type: value })
|
||||
}
|
||||
})
|
||||
it('keeps unknown values and exact presets without duplicates', () => {
|
||||
expect(buildPlanTypeOptions('future_sku', 'Clear').at(-1)).toEqual({ value: 'future_sku', label: 'future_sku' })
|
||||
for (const value of ['promax', 'team', 'ent26', 'edu_plus']) {
|
||||
expect(buildPlanTypeOptions(value, 'Clear').filter(option => option.value === value)).toHaveLength(1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -574,3 +539,14 @@ describe('plan_type helpers', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Codex subscription analytics labels', () => {
|
||||
it.each([
|
||||
['business', 'Business'], ['self_serve_business_prolite', 'Business'],
|
||||
['enterprise_cbp_automation', 'Enterprise'], ['ent26', 'Enterprise'],
|
||||
['edu', 'Education'], ['edu_plus', 'Education'], ['edu_pro', 'Education'],
|
||||
['unknown', 'Account'], ['promax', 'Pro 500']
|
||||
])('groups %s without changing its status label', (sku, label) => {
|
||||
expect(openAIPlanTypeLabel(sku, 'analytics')).toBe(label)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { openAIPlanTypeLabel } from '@/utils/planType'
|
||||
import { openAIPlanTypeKey, openAIPlanTypeLabel, openAIPlanTypes } from '@/utils/planType'
|
||||
|
||||
export function applyInterceptWarmup(
|
||||
credentials: Record<string, unknown>,
|
||||
@@ -503,7 +503,7 @@ export interface PlanTypeOption {
|
||||
/**
|
||||
* plan_type 值的友好显示标签(ChatGPT 档位命名)。
|
||||
* 与 PlatformTypeBadge 共用 openAIPlanTypeLabel,避免两处映射漂移;
|
||||
* canonical 值 chatgptpro 显示为 Pro 20x,team 显示为 Business Standard。未知值原样返回。
|
||||
* canonical 值 chatgptpro 显示为 Pro 200,team 显示为 Business。未知值原样返回。
|
||||
*/
|
||||
export function planTypeDisplayLabel(value: string): string {
|
||||
return openAIPlanTypeLabel(value) || value
|
||||
@@ -518,32 +518,16 @@ export function readPlanType(credentials: Record<string, unknown> | undefined |
|
||||
return typeof v === 'string' ? v : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 plan_type 下拉选项:清空 + Plus/Pro 20x/Pro 5x/Business Premium/Free 预设。
|
||||
* 若当前值是某预设的别名(如 chatgptpro↔Pro 20x),用当前的 canonical 值占据该
|
||||
* 标签位(保留 canonical,显示友好标签,避免重复项);若是完全预设外的值
|
||||
* (如 team 或异常值),追加为一项,避免编辑时下拉丢失原值。
|
||||
*/
|
||||
/** Build SKU-preserving choices; aliases replace only the same canonical SKU. */
|
||||
export function buildPlanTypeOptions(current: string, clearLabel: string): PlanTypeOption[] {
|
||||
const cur = (current || '').trim()
|
||||
const curLabel = cur ? planTypeDisplayLabel(cur) : ''
|
||||
const presets: PlanTypeOption[] = [
|
||||
{ value: 'plus', label: 'Plus' },
|
||||
{ value: 'pro', label: 'Pro 20x' },
|
||||
{ value: 'prolite', label: 'Pro 5x' },
|
||||
{ value: 'self_serve_business_prolite', label: 'Business Premium' },
|
||||
{ value: 'free', label: 'Free' }
|
||||
]
|
||||
const key = openAIPlanTypeKey(cur)
|
||||
const opts: PlanTypeOption[] = [{ value: '', label: clearLabel }]
|
||||
for (const p of presets) {
|
||||
if (cur && p.value !== cur.toLowerCase() && p.label === curLabel) {
|
||||
// 当前值是该预设的别名:用 canonical 当前值占位,标签仍显示友好名
|
||||
opts.push({ value: cur, label: p.label })
|
||||
} else {
|
||||
opts.push(p)
|
||||
}
|
||||
for (const preset of openAIPlanTypes) {
|
||||
const value = cur && key === openAIPlanTypeKey(preset) ? cur : preset
|
||||
opts.push({ value, label: planTypeDisplayLabel(value) })
|
||||
}
|
||||
if (cur && !opts.some(o => o.value.toLowerCase() === cur.toLowerCase())) {
|
||||
if (cur && !opts.some(option => option.value === cur)) {
|
||||
opts.push({ value: cur, label: planTypeDisplayLabel(cur) })
|
||||
}
|
||||
return opts
|
||||
|
||||
@@ -118,7 +118,7 @@ const normalizedPlanType = computed(() => normalizePlanType(props.planType))
|
||||
|
||||
const planLabel = computed(() => {
|
||||
if (!normalizedPlanType.value) return ''
|
||||
// ChatGPT 档位命名(Pro 5x / Pro 20x、Business Standard / Business Premium)只适用于
|
||||
// ChatGPT 档位命名(Pro 100 / Pro 200 / Pro 500、Business / Business Premium)只适用于
|
||||
// OpenAI:Antigravity 与 Grok 各自的 pro/team 沿用下面的通用标签。
|
||||
if (props.platform === 'openai') {
|
||||
const label = openAIPlanTypeLabel(props.planType)
|
||||
@@ -259,13 +259,15 @@ const planBadgeClass = computed(() => {
|
||||
if (normalizedPlanType.value === 'plus') {
|
||||
return 'bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-300'
|
||||
}
|
||||
if (normalizedPlanType.value === 'team' || normalizedPlanType.value === 'selfservebusinessprolite') {
|
||||
if (normalizedPlanType.value === 'team' || normalizedPlanType.value === 'selfservebusinessprolite' ||
|
||||
(props.platform === 'openai' && ['selfservebusinessusagebased', 'business', 'enterprise', 'ent26', 'enterprisecbpautomation', 'enterprisecbpusagebased', 'edu', 'eduplus', 'edupro'].includes(normalizedPlanType.value))) {
|
||||
return 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300'
|
||||
}
|
||||
if (
|
||||
normalizedPlanType.value === 'pro' ||
|
||||
normalizedPlanType.value === 'chatgptpro' ||
|
||||
normalizedPlanType.value === 'prolite'
|
||||
normalizedPlanType.value === 'prolite' ||
|
||||
(props.platform === 'openai' && normalizedPlanType.value === 'promax')
|
||||
) {
|
||||
return 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300'
|
||||
}
|
||||
|
||||
@@ -19,29 +19,29 @@ function mountPlan(platform: AccountPlatform, planType: string) {
|
||||
}
|
||||
|
||||
describe('PlatformTypeBadge ChatGPT plan tiers', () => {
|
||||
it('labels pro / chatgptpro as Pro 20x with the Pro color', () => {
|
||||
it('labels pro / chatgptpro as Pro 200 with the Pro color', () => {
|
||||
for (const planType of ['pro', 'chatgptpro', 'PRO']) {
|
||||
const wrapper = mountPlan('openai', planType)
|
||||
|
||||
expect(wrapper.text()).toContain('Pro 20x')
|
||||
expect(wrapper.text()).toContain('Pro 200')
|
||||
expect(wrapper.html()).toContain('bg-violet-100')
|
||||
}
|
||||
})
|
||||
|
||||
it('labels prolite as Pro 5x sharing the Pro color', () => {
|
||||
it('labels prolite as Pro 100 sharing the Pro color', () => {
|
||||
for (const planType of ['prolite', 'PROLITE', 'pro_lite']) {
|
||||
const wrapper = mountPlan('openai', planType)
|
||||
|
||||
expect(wrapper.text()).toContain('Pro 5x')
|
||||
expect(wrapper.text()).toContain('Pro 100')
|
||||
expect(wrapper.html()).toContain('bg-violet-100')
|
||||
expect(wrapper.text()).not.toContain('Pro 20x')
|
||||
expect(wrapper.text()).not.toContain('Pro 200')
|
||||
}
|
||||
})
|
||||
|
||||
it('labels team as Business Standard with the Team color', () => {
|
||||
it('labels team as Business with the Team color', () => {
|
||||
const wrapper = mountPlan('openai', 'team')
|
||||
|
||||
expect(wrapper.text()).toContain('Business Standard')
|
||||
expect(wrapper.text()).toContain('Business')
|
||||
expect(wrapper.html()).toContain('bg-indigo-100')
|
||||
})
|
||||
|
||||
@@ -70,24 +70,41 @@ describe('PlatformTypeBadge ChatGPT plan tiers', () => {
|
||||
})
|
||||
|
||||
it('falls back to the raw value for unknown plans', () => {
|
||||
const wrapper = mountPlan('openai', 'enterprise')
|
||||
const wrapper = mountPlan('openai', 'future_sku')
|
||||
|
||||
expect(wrapper.text()).toContain('enterprise')
|
||||
expect(wrapper.text()).toContain('future_sku')
|
||||
expect(wrapper.html()).not.toContain('bg-violet-100')
|
||||
expect(wrapper.html()).not.toContain('bg-indigo-100')
|
||||
})
|
||||
|
||||
it('does not apply the ChatGPT tier naming to other platforms', () => {
|
||||
// Antigravity 的 Pro 与 Grok 的 pro 是各自产品线的档位,不能显示成 Pro 20x。
|
||||
// Antigravity 的 Pro 与 Grok 的 pro 是各自产品线的档位,不能显示成 Pro 200。
|
||||
for (const platform of ['antigravity', 'grok'] as AccountPlatform[]) {
|
||||
const wrapper = mountPlan(platform, 'pro')
|
||||
|
||||
expect(wrapper.text()).toContain('Pro')
|
||||
expect(wrapper.text()).not.toContain('Pro 20x')
|
||||
expect(wrapper.text()).not.toContain('Pro 200')
|
||||
}
|
||||
|
||||
const antigravityTeam = mountPlan('antigravity', 'team')
|
||||
expect(antigravityTeam.text()).toContain('Team')
|
||||
expect(antigravityTeam.text()).not.toContain('Business Standard')
|
||||
expect(antigravityTeam.text()).not.toContain('Business')
|
||||
})
|
||||
})
|
||||
|
||||
describe('new Codex subscription labels', () => {
|
||||
it.each([
|
||||
['promax', 'Pro 500', 'bg-violet-100'],
|
||||
['go', 'Go', ''],
|
||||
['ent26', 'Enterprise', 'bg-indigo-100'],
|
||||
['enterprise_cbp_automation', 'Enterprise (Automation)', 'bg-indigo-100'],
|
||||
['self_serve_business_usage_based', 'Business', 'bg-indigo-100'],
|
||||
['business', 'Enterprise', 'bg-indigo-100'],
|
||||
['edu_plus', 'Edu Plus', 'bg-indigo-100'],
|
||||
['edu_pro', 'Edu Pro', 'bg-indigo-100']
|
||||
])('displays %s', (sku, label, color) => {
|
||||
const wrapper = mountPlan('openai', sku)
|
||||
expect(wrapper.text()).toContain(label)
|
||||
if (color) expect(wrapper.html()).toContain(color)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
/**
|
||||
* ChatGPT(OpenAI)订阅档位 plan_type 的解析与展示映射。
|
||||
*
|
||||
* plan_type 由上游原样透传,同一档位会出现 `chatgpt_pro`、`self_serve_business_prolite`
|
||||
* 这类下划线/别名写法,因此匹配前一律先归一化。
|
||||
*
|
||||
* 这里的档位命名只对 OpenAI 平台成立:Antigravity 的 `Pro`、Grok 的 `pro` 是各自产品线的
|
||||
* 档位,套用 ChatGPT 的倍率命名(Pro 5x / Pro 20x)会显示错误。
|
||||
*/
|
||||
|
||||
/**
|
||||
* plan_type 归一化:去首尾空白、转小写,并去掉空格/下划线/连字符。
|
||||
* 例:`self_serve_business_prolite` → `selfservebusinessprolite`。
|
||||
*/
|
||||
/** OpenAI subscription SKU identity and Codex display names. Other providers use their own labels. */
|
||||
export function normalizePlanType(value?: string | null): string {
|
||||
return (value || '').trim().toLowerCase().replace(/[\s_-]+/g, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* ChatGPT 档位 → 展示标签;未知档位返回空串,由调用方决定是否回退为原始值。
|
||||
*
|
||||
* Pro 的倍率命名:`pro`/`chatgptpro` 为 Pro 20x,`prolite` 为 Pro 5x;
|
||||
* Team/Business:`team` 为 Business Standard,`self_serve_business_prolite` 为 Business Premium。
|
||||
*/
|
||||
export function openAIPlanTypeLabel(value?: string | null): string {
|
||||
switch (normalizePlanType(value)) {
|
||||
case 'plus':
|
||||
return 'Plus'
|
||||
case 'chatgptpro':
|
||||
case 'pro':
|
||||
return 'Pro 20x'
|
||||
case 'prolite':
|
||||
return 'Pro 5x'
|
||||
case 'selfservebusinessprolite':
|
||||
return 'Business Premium'
|
||||
/** Canonical comparison key; labels may be shared by distinct SKUs. */
|
||||
export function openAIPlanTypeKey(value?: string | null): string {
|
||||
const key = normalizePlanType(value)
|
||||
return key === 'chatgptpro' ? 'pro' : key
|
||||
}
|
||||
|
||||
// Wire names from openai/codex b1e72963, protocol/src/account.rs.
|
||||
export const openAIPlanTypes = [
|
||||
'free', 'go', 'plus', 'prolite', 'pro', 'promax', 'team',
|
||||
'self_serve_business_usage_based', 'self_serve_business_prolite', 'business',
|
||||
'enterprise', 'ent26', 'enterprise_cbp_usage_based', 'enterprise_cbp_automation',
|
||||
'edu', 'edu_plus', 'edu_pro', 'unknown'
|
||||
] as const
|
||||
|
||||
/** Keep Status SKU names distinct from the account families used in analytics. */
|
||||
export function openAIPlanTypeLabel(value?: string | null, display: 'status' | 'analytics' = 'status'): string {
|
||||
switch (openAIPlanTypeKey(value)) {
|
||||
case 'free': return 'Free'
|
||||
case 'go': return 'Go'
|
||||
case 'plus': return 'Plus'
|
||||
case 'prolite': return 'Pro 100'
|
||||
case 'pro': return 'Pro 200'
|
||||
case 'promax': return 'Pro 500'
|
||||
case 'team':
|
||||
return 'Business Standard'
|
||||
case 'free':
|
||||
return 'Free'
|
||||
default:
|
||||
return ''
|
||||
case 'selfservebusinessusagebased': return 'Business'
|
||||
case 'business': return display === 'status' ? 'Enterprise' : 'Business'
|
||||
case 'selfservebusinessprolite': return display === 'status' ? 'Business Premium' : 'Business'
|
||||
case 'enterprisecbpautomation': return display === 'status' ? 'Enterprise (Automation)' : 'Enterprise'
|
||||
case 'enterprise':
|
||||
case 'ent26':
|
||||
case 'enterprisecbpusagebased': return 'Enterprise'
|
||||
case 'edu': return display === 'status' ? 'Edu' : 'Education'
|
||||
case 'eduplus': return display === 'status' ? 'Edu Plus' : 'Education'
|
||||
case 'edupro': return display === 'status' ? 'Edu Pro' : 'Education'
|
||||
case 'unknown': return display === 'status' ? 'Unknown' : 'Account'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user