|
|
|
@@ -20,13 +20,17 @@ import type {
|
|
|
|
|
CodexUsageWindow,
|
|
|
|
|
CodexQuotaWindow,
|
|
|
|
|
CodexUsagePayload,
|
|
|
|
|
GeminiCliCodeAssistPayload,
|
|
|
|
|
GeminiCliCredits,
|
|
|
|
|
GeminiCliParsedBucket,
|
|
|
|
|
GeminiCliQuotaBucketState,
|
|
|
|
|
GeminiCliQuotaState,
|
|
|
|
|
GeminiCliUserTier,
|
|
|
|
|
KimiQuotaRow,
|
|
|
|
|
KimiQuotaState,
|
|
|
|
|
} from '@/types';
|
|
|
|
|
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
|
|
|
|
|
import { useQuotaStore } from '@/stores';
|
|
|
|
|
import {
|
|
|
|
|
ANTIGRAVITY_QUOTA_URLS,
|
|
|
|
|
ANTIGRAVITY_REQUEST_HEADERS,
|
|
|
|
@@ -37,6 +41,7 @@ import {
|
|
|
|
|
CODEX_USAGE_URL,
|
|
|
|
|
CODEX_REQUEST_HEADERS,
|
|
|
|
|
GEMINI_CLI_QUOTA_URL,
|
|
|
|
|
GEMINI_CLI_CODE_ASSIST_URL,
|
|
|
|
|
GEMINI_CLI_REQUEST_HEADERS,
|
|
|
|
|
KIMI_USAGE_URL,
|
|
|
|
|
KIMI_REQUEST_HEADERS,
|
|
|
|
@@ -49,6 +54,7 @@ import {
|
|
|
|
|
parseClaudeUsagePayload,
|
|
|
|
|
parseCodexUsagePayload,
|
|
|
|
|
parseGeminiCliQuotaPayload,
|
|
|
|
|
parseGeminiCliCodeAssistPayload,
|
|
|
|
|
parseKimiUsagePayload,
|
|
|
|
|
resolveCodexChatgptAccountId,
|
|
|
|
|
resolveCodexPlanType,
|
|
|
|
@@ -78,6 +84,11 @@ type QuotaUpdater<T> = T | ((prev: T) => T);
|
|
|
|
|
type QuotaType = 'antigravity' | 'claude' | 'codex' | 'gemini-cli' | 'kimi';
|
|
|
|
|
|
|
|
|
|
const DEFAULT_ANTIGRAVITY_PROJECT_ID = 'bamboo-precept-lgxtn';
|
|
|
|
|
const geminiCliSupplementaryRequestIds = new Map<string, number>();
|
|
|
|
|
const geminiCliSupplementaryCache = new Map<
|
|
|
|
|
string,
|
|
|
|
|
{ requestId: number; tierLabel: string | null; creditBalance: number | null }
|
|
|
|
|
>();
|
|
|
|
|
|
|
|
|
|
export interface QuotaStore {
|
|
|
|
|
antigravityQuota: Record<string, AntigravityQuotaState>;
|
|
|
|
@@ -427,10 +438,161 @@ const fetchCodexQuota = async (
|
|
|
|
|
return { planType: planTypeFromUsage ?? planTypeFromFile, windows };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const GEMINI_CLI_G1_CREDIT_TYPE = 'GOOGLE_ONE_AI';
|
|
|
|
|
|
|
|
|
|
const GEMINI_CLI_TIER_LABELS: Record<string, string> = {
|
|
|
|
|
'free-tier': 'tier_free',
|
|
|
|
|
'legacy-tier': 'tier_legacy',
|
|
|
|
|
'standard-tier': 'tier_standard',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resolveGeminiCliTierLabel = (
|
|
|
|
|
payload: GeminiCliCodeAssistPayload | null,
|
|
|
|
|
t: TFunction
|
|
|
|
|
): string | null => {
|
|
|
|
|
if (!payload) return null;
|
|
|
|
|
const currentTier: GeminiCliUserTier | null | undefined =
|
|
|
|
|
payload.currentTier ?? payload.current_tier;
|
|
|
|
|
const paidTier: GeminiCliUserTier | null | undefined =
|
|
|
|
|
payload.paidTier ?? payload.paid_tier;
|
|
|
|
|
const tierId = normalizeStringValue(paidTier?.id) ?? normalizeStringValue(currentTier?.id);
|
|
|
|
|
if (!tierId) return null;
|
|
|
|
|
const labelKey = GEMINI_CLI_TIER_LABELS[tierId];
|
|
|
|
|
return labelKey ? t(`gemini_cli_quota.${labelKey}`) : tierId;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resolveGeminiCliCreditBalance = (
|
|
|
|
|
payload: GeminiCliCodeAssistPayload | null
|
|
|
|
|
): number | null => {
|
|
|
|
|
if (!payload) return null;
|
|
|
|
|
const paidTier: GeminiCliUserTier | null | undefined =
|
|
|
|
|
payload.paidTier ?? payload.paid_tier;
|
|
|
|
|
const currentTier: GeminiCliUserTier | null | undefined =
|
|
|
|
|
payload.currentTier ?? payload.current_tier;
|
|
|
|
|
const tier = paidTier ?? currentTier;
|
|
|
|
|
if (!tier) return null;
|
|
|
|
|
const credits: GeminiCliCredits[] =
|
|
|
|
|
tier.availableCredits ?? tier.available_credits ?? [];
|
|
|
|
|
let total = 0;
|
|
|
|
|
let found = false;
|
|
|
|
|
for (const credit of credits) {
|
|
|
|
|
const creditType = normalizeStringValue(credit.creditType ?? credit.credit_type);
|
|
|
|
|
if (creditType !== GEMINI_CLI_G1_CREDIT_TYPE) continue;
|
|
|
|
|
const amount = normalizeNumberValue(credit.creditAmount ?? credit.credit_amount);
|
|
|
|
|
if (amount !== null) {
|
|
|
|
|
total += amount;
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return found ? total : null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchGeminiCliCodeAssist = async (
|
|
|
|
|
authIndex: string,
|
|
|
|
|
projectId: string,
|
|
|
|
|
t: TFunction
|
|
|
|
|
): Promise<{ tierLabel: string | null; creditBalance: number | null }> => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await apiCallApi.request({
|
|
|
|
|
authIndex,
|
|
|
|
|
method: 'POST',
|
|
|
|
|
url: GEMINI_CLI_CODE_ASSIST_URL,
|
|
|
|
|
header: { ...GEMINI_CLI_REQUEST_HEADERS },
|
|
|
|
|
data: JSON.stringify({
|
|
|
|
|
cloudaicompanionProject: projectId,
|
|
|
|
|
metadata: {
|
|
|
|
|
ideType: 'IDE_UNSPECIFIED',
|
|
|
|
|
platform: 'PLATFORM_UNSPECIFIED',
|
|
|
|
|
pluginType: 'GEMINI',
|
|
|
|
|
duetProject: projectId,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
|
|
|
return { tierLabel: null, creditBalance: null };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payload = parseGeminiCliCodeAssistPayload(result.body ?? result.bodyText);
|
|
|
|
|
return {
|
|
|
|
|
tierLabel: resolveGeminiCliTierLabel(payload, t),
|
|
|
|
|
creditBalance: resolveGeminiCliCreditBalance(payload),
|
|
|
|
|
};
|
|
|
|
|
} catch {
|
|
|
|
|
return { tierLabel: null, creditBalance: null };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const readGeminiCliSupplementarySnapshot = (
|
|
|
|
|
fileName: string,
|
|
|
|
|
requestId: number
|
|
|
|
|
): { tierLabel: string | null; creditBalance: number | null } => {
|
|
|
|
|
const cached = geminiCliSupplementaryCache.get(fileName);
|
|
|
|
|
if (!cached || cached.requestId !== requestId) {
|
|
|
|
|
return { tierLabel: null, creditBalance: null };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
tierLabel: cached.tierLabel,
|
|
|
|
|
creditBalance: cached.creditBalance,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const scheduleGeminiCliSupplementaryRefresh = (
|
|
|
|
|
fileName: string,
|
|
|
|
|
authIndex: string,
|
|
|
|
|
projectId: string,
|
|
|
|
|
t: TFunction
|
|
|
|
|
): number => {
|
|
|
|
|
const requestId = (geminiCliSupplementaryRequestIds.get(fileName) ?? 0) + 1;
|
|
|
|
|
geminiCliSupplementaryRequestIds.set(fileName, requestId);
|
|
|
|
|
geminiCliSupplementaryCache.delete(fileName);
|
|
|
|
|
|
|
|
|
|
void (async () => {
|
|
|
|
|
const supplementary = await fetchGeminiCliCodeAssist(authIndex, projectId, t);
|
|
|
|
|
if (geminiCliSupplementaryRequestIds.get(fileName) !== requestId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
geminiCliSupplementaryCache.set(fileName, { requestId, ...supplementary });
|
|
|
|
|
|
|
|
|
|
useQuotaStore.getState().setGeminiCliQuota((prev) => {
|
|
|
|
|
const current = prev[fileName];
|
|
|
|
|
if (!current || current.status !== 'success') {
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
current.tierLabel === supplementary.tierLabel &&
|
|
|
|
|
current.creditBalance === supplementary.creditBalance
|
|
|
|
|
) {
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
[fileName]: {
|
|
|
|
|
...current,
|
|
|
|
|
tierLabel: supplementary.tierLabel,
|
|
|
|
|
creditBalance: supplementary.creditBalance,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
return requestId;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fetchGeminiCliQuota = async (
|
|
|
|
|
file: AuthFileItem,
|
|
|
|
|
t: TFunction
|
|
|
|
|
): Promise<GeminiCliQuotaBucketState[]> => {
|
|
|
|
|
): Promise<{
|
|
|
|
|
fileName: string;
|
|
|
|
|
supplementaryRequestId: number;
|
|
|
|
|
buckets: GeminiCliQuotaBucketState[];
|
|
|
|
|
tierLabel: string | null;
|
|
|
|
|
creditBalance: number | null;
|
|
|
|
|
}> => {
|
|
|
|
|
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
|
|
|
|
const authIndex = normalizeAuthIndex(rawAuthIndex);
|
|
|
|
|
if (!authIndex) {
|
|
|
|
@@ -442,21 +604,19 @@ const fetchGeminiCliQuota = async (
|
|
|
|
|
throw new Error(t('gemini_cli_quota.missing_project_id'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await apiCallApi.request({
|
|
|
|
|
const quotaResponse = await apiCallApi.request({
|
|
|
|
|
authIndex,
|
|
|
|
|
method: 'POST',
|
|
|
|
|
url: GEMINI_CLI_QUOTA_URL,
|
|
|
|
|
header: { ...GEMINI_CLI_REQUEST_HEADERS },
|
|
|
|
|
data: JSON.stringify({ project: projectId }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
|
|
|
throw createStatusError(getApiCallErrorMessage(result), result.statusCode);
|
|
|
|
|
if (quotaResponse.statusCode < 200 || quotaResponse.statusCode >= 300) {
|
|
|
|
|
throw createStatusError(getApiCallErrorMessage(quotaResponse), quotaResponse.statusCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payload = parseGeminiCliQuotaPayload(result.body ?? result.bodyText);
|
|
|
|
|
const payload = parseGeminiCliQuotaPayload(quotaResponse.body ?? quotaResponse.bodyText);
|
|
|
|
|
const buckets = Array.isArray(payload?.buckets) ? payload?.buckets : [];
|
|
|
|
|
if (buckets.length === 0) return [];
|
|
|
|
|
|
|
|
|
|
const parsedBuckets = buckets
|
|
|
|
|
.map((bucket) => {
|
|
|
|
@@ -487,7 +647,25 @@ const fetchGeminiCliQuota = async (
|
|
|
|
|
})
|
|
|
|
|
.filter((bucket): bucket is GeminiCliParsedBucket => bucket !== null);
|
|
|
|
|
|
|
|
|
|
return buildGeminiCliQuotaBuckets(parsedBuckets);
|
|
|
|
|
const builtBuckets = buildGeminiCliQuotaBuckets(parsedBuckets);
|
|
|
|
|
const supplementaryRequestId = scheduleGeminiCliSupplementaryRefresh(
|
|
|
|
|
file.name,
|
|
|
|
|
authIndex,
|
|
|
|
|
projectId,
|
|
|
|
|
t
|
|
|
|
|
);
|
|
|
|
|
const supplementarySnapshot = readGeminiCliSupplementarySnapshot(
|
|
|
|
|
file.name,
|
|
|
|
|
supplementaryRequestId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
fileName: file.name,
|
|
|
|
|
supplementaryRequestId,
|
|
|
|
|
buckets: builtBuckets,
|
|
|
|
|
tierLabel: supplementarySnapshot.tierLabel,
|
|
|
|
|
creditBalance: supplementarySnapshot.creditBalance,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const renderAntigravityItems = (
|
|
|
|
@@ -605,50 +783,86 @@ const renderGeminiCliItems = (
|
|
|
|
|
helpers: QuotaRenderHelpers
|
|
|
|
|
): ReactNode => {
|
|
|
|
|
const { styles: styleMap, QuotaProgressBar } = helpers;
|
|
|
|
|
const { createElement: h } = React;
|
|
|
|
|
const { createElement: h, Fragment } = React;
|
|
|
|
|
const buckets = quota.buckets ?? [];
|
|
|
|
|
const tierLabel = quota.tierLabel ?? null;
|
|
|
|
|
const creditBalance = quota.creditBalance ?? null;
|
|
|
|
|
const nodes: ReactNode[] = [];
|
|
|
|
|
|
|
|
|
|
if (buckets.length === 0) {
|
|
|
|
|
return h('div', { className: styleMap.quotaMessage }, t('gemini_cli_quota.empty_buckets'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buckets.map((bucket) => {
|
|
|
|
|
const fraction = bucket.remainingFraction;
|
|
|
|
|
const clamped = fraction === null ? null : Math.max(0, Math.min(1, fraction));
|
|
|
|
|
const percent = clamped === null ? null : Math.round(clamped * 100);
|
|
|
|
|
const percentLabel = percent === null ? '--' : `${percent}%`;
|
|
|
|
|
const remainingAmountLabel =
|
|
|
|
|
bucket.remainingAmount === null || bucket.remainingAmount === undefined
|
|
|
|
|
? null
|
|
|
|
|
: t('gemini_cli_quota.remaining_amount', {
|
|
|
|
|
count: bucket.remainingAmount,
|
|
|
|
|
});
|
|
|
|
|
const titleBase =
|
|
|
|
|
bucket.modelIds && bucket.modelIds.length > 0 ? bucket.modelIds.join(', ') : bucket.label;
|
|
|
|
|
const title = bucket.tokenType ? `${titleBase} (${bucket.tokenType})` : titleBase;
|
|
|
|
|
|
|
|
|
|
const resetLabel = formatQuotaResetTime(bucket.resetTime);
|
|
|
|
|
|
|
|
|
|
return h(
|
|
|
|
|
'div',
|
|
|
|
|
{ key: bucket.id, className: styleMap.quotaRow },
|
|
|
|
|
if (tierLabel) {
|
|
|
|
|
nodes.push(
|
|
|
|
|
h(
|
|
|
|
|
'div',
|
|
|
|
|
{ className: styleMap.quotaRowHeader },
|
|
|
|
|
h('span', { className: styleMap.quotaModel, title }, bucket.label),
|
|
|
|
|
{ key: 'tier', className: styleMap.codexPlan },
|
|
|
|
|
h('span', { className: styleMap.codexPlanLabel }, t('gemini_cli_quota.tier_label')),
|
|
|
|
|
h('span', { className: styleMap.codexPlanValue }, tierLabel)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (creditBalance !== null) {
|
|
|
|
|
nodes.push(
|
|
|
|
|
h(
|
|
|
|
|
'div',
|
|
|
|
|
{ key: 'credits', className: styleMap.codexPlan },
|
|
|
|
|
h('span', { className: styleMap.codexPlanLabel }, t('gemini_cli_quota.credit_label')),
|
|
|
|
|
h(
|
|
|
|
|
'span',
|
|
|
|
|
{ className: styleMap.codexPlanValue },
|
|
|
|
|
t('gemini_cli_quota.credit_amount', { count: creditBalance })
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (buckets.length === 0) {
|
|
|
|
|
nodes.push(
|
|
|
|
|
h('div', { key: 'empty', className: styleMap.quotaMessage }, t('gemini_cli_quota.empty_buckets'))
|
|
|
|
|
);
|
|
|
|
|
return h(Fragment, null, ...nodes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nodes.push(
|
|
|
|
|
...buckets.map((bucket) => {
|
|
|
|
|
const fraction = bucket.remainingFraction;
|
|
|
|
|
const clamped = fraction === null ? null : Math.max(0, Math.min(1, fraction));
|
|
|
|
|
const percent = clamped === null ? null : Math.round(clamped * 100);
|
|
|
|
|
const percentLabel = percent === null ? '--' : `${percent}%`;
|
|
|
|
|
const remainingAmountLabel =
|
|
|
|
|
bucket.remainingAmount === null || bucket.remainingAmount === undefined
|
|
|
|
|
? null
|
|
|
|
|
: t('gemini_cli_quota.remaining_amount', {
|
|
|
|
|
count: bucket.remainingAmount,
|
|
|
|
|
});
|
|
|
|
|
const titleBase =
|
|
|
|
|
bucket.modelIds && bucket.modelIds.length > 0 ? bucket.modelIds.join(', ') : bucket.label;
|
|
|
|
|
const title = bucket.tokenType ? `${titleBase} (${bucket.tokenType})` : titleBase;
|
|
|
|
|
|
|
|
|
|
const resetLabel = formatQuotaResetTime(bucket.resetTime);
|
|
|
|
|
|
|
|
|
|
return h(
|
|
|
|
|
'div',
|
|
|
|
|
{ key: bucket.id, className: styleMap.quotaRow },
|
|
|
|
|
h(
|
|
|
|
|
'div',
|
|
|
|
|
{ className: styleMap.quotaMeta },
|
|
|
|
|
h('span', { className: styleMap.quotaPercent }, percentLabel),
|
|
|
|
|
remainingAmountLabel
|
|
|
|
|
? h('span', { className: styleMap.quotaAmount }, remainingAmountLabel)
|
|
|
|
|
: null,
|
|
|
|
|
h('span', { className: styleMap.quotaReset }, resetLabel)
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
h(QuotaProgressBar, { percent, highThreshold: 60, mediumThreshold: 20 })
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
{ className: styleMap.quotaRowHeader },
|
|
|
|
|
h('span', { className: styleMap.quotaModel, title }, bucket.label),
|
|
|
|
|
h(
|
|
|
|
|
'div',
|
|
|
|
|
{ className: styleMap.quotaMeta },
|
|
|
|
|
h('span', { className: styleMap.quotaPercent }, percentLabel),
|
|
|
|
|
remainingAmountLabel
|
|
|
|
|
? h('span', { className: styleMap.quotaAmount }, remainingAmountLabel)
|
|
|
|
|
: null,
|
|
|
|
|
h('span', { className: styleMap.quotaReset }, resetLabel)
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
h(QuotaProgressBar, { percent, highThreshold: 60, mediumThreshold: 20 })
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return h(Fragment, null, ...nodes);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const buildClaudeQuotaWindows = (
|
|
|
|
@@ -927,7 +1141,16 @@ export const CODEX_CONFIG: QuotaConfig<
|
|
|
|
|
renderQuotaItems: renderCodexItems,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const GEMINI_CLI_CONFIG: QuotaConfig<GeminiCliQuotaState, GeminiCliQuotaBucketState[]> = {
|
|
|
|
|
export const GEMINI_CLI_CONFIG: QuotaConfig<
|
|
|
|
|
GeminiCliQuotaState,
|
|
|
|
|
{
|
|
|
|
|
fileName: string;
|
|
|
|
|
supplementaryRequestId: number;
|
|
|
|
|
buckets: GeminiCliQuotaBucketState[];
|
|
|
|
|
tierLabel: string | null;
|
|
|
|
|
creditBalance: number | null;
|
|
|
|
|
}
|
|
|
|
|
> = {
|
|
|
|
|
type: 'gemini-cli',
|
|
|
|
|
i18nPrefix: 'gemini_cli_quota',
|
|
|
|
|
cardIdleMessageKey: 'quota_management.card_idle_hint',
|
|
|
|
@@ -936,8 +1159,20 @@ export const GEMINI_CLI_CONFIG: QuotaConfig<GeminiCliQuotaState, GeminiCliQuotaB
|
|
|
|
|
fetchQuota: fetchGeminiCliQuota,
|
|
|
|
|
storeSelector: (state) => state.geminiCliQuota,
|
|
|
|
|
storeSetter: 'setGeminiCliQuota',
|
|
|
|
|
buildLoadingState: () => ({ status: 'loading', buckets: [] }),
|
|
|
|
|
buildSuccessState: (buckets) => ({ status: 'success', buckets }),
|
|
|
|
|
buildLoadingState: () => ({ status: 'loading', buckets: [], tierLabel: null, creditBalance: null }),
|
|
|
|
|
buildSuccessState: (data) => {
|
|
|
|
|
const supplementarySnapshot = readGeminiCliSupplementarySnapshot(
|
|
|
|
|
data.fileName,
|
|
|
|
|
data.supplementaryRequestId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
status: 'success',
|
|
|
|
|
buckets: data.buckets,
|
|
|
|
|
tierLabel: supplementarySnapshot.tierLabel ?? data.tierLabel,
|
|
|
|
|
creditBalance: supplementarySnapshot.creditBalance ?? data.creditBalance,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
buildErrorState: (message, status) => ({
|
|
|
|
|
status: 'error',
|
|
|
|
|
buckets: [],
|
|
|
|
|