diff --git a/.claude/memory.md b/.claude/memory.md index 554f7e579f..d0cb6f9818 100644 --- a/.claude/memory.md +++ b/.claude/memory.md @@ -87,6 +87,8 @@ Quick reference for anyone starting with Claude on this project. Updated by the - **Banner dismiss state uses localStorage** (prefix `openhuman:upsell:`), not Redux — consistent with CLAUDE.md exception for ephemeral UI state. - **Phased rollout** — Phase 1 = banners + limit modal + hook. Phase 2 = onboarding upsell + analytics. Phase 3 = remote config + A/B testing. - **"5-hour" label stragglers in Conversations.tsx** — `LimitPill` label and its hover tooltip still say "5h" / "5-hour". Commit 8c52236's "10-hour" terminology refactor missed those two spots. +- **`getTeamUsage()` now normalizes via `normalizeTeamUsage()`** — Added in issue #482. The Rust sidecar passes backend JSON through opaquely (`src/openhuman/team/ops.rs`), so the TS client must normalize field names and types. Pattern matches existing `normalizeCreditBalance()` in the same file. Any new billing API that returns raw backend data should follow the same normalize-at-the-client pattern. +- **Two separate `TeamUsage` types exist** — `creditsApi.ts:24` (billing: cycle budget, limits) and `types/team.ts:11` (team model: daily token limit). Different import paths, no collision, but confusing. ## Settings & Skills Reorganization (Issue #396) diff --git a/app/src/components/settings/panels/BillingPanel.tsx b/app/src/components/settings/panels/BillingPanel.tsx index ec8a98f55d..d0d06295b3 100644 --- a/app/src/components/settings/panels/BillingPanel.tsx +++ b/app/src/components/settings/panels/BillingPanel.tsx @@ -88,21 +88,34 @@ const BillingPanel = () => { // Fetch current plan, credits balance, and team usage on mount useEffect(() => { setIsLoadingCredits(true); - Promise.all([billingApi.getCurrentPlan(), creditsApi.getBalance(), creditsApi.getTeamUsage()]) - .then(([plan, balance, usage]) => { - log( - '[load] plan=%s active=%s weeklyBudget=%s', - plan.plan, - plan.hasActiveSubscription, - plan.weeklyBudgetUsd - ); - setCurrentPlan(plan); - setCreditBalance(balance); - setTeamUsage(usage); - }) - .catch(error => { - log('[load] failed: %O', error); - console.error(error); + Promise.allSettled([ + billingApi.getCurrentPlan(), + creditsApi.getBalance(), + creditsApi.getTeamUsage(), + ]) + .then(([planResult, balanceResult, usageResult]) => { + if (planResult.status === 'fulfilled') { + const plan = planResult.value; + log( + '[load] plan=%s active=%s weeklyBudget=%s', + plan.plan, + plan.hasActiveSubscription, + plan.weeklyBudgetUsd + ); + setCurrentPlan(plan); + } else { + log('[load] getCurrentPlan failed: %O', planResult.reason); + } + if (balanceResult.status === 'fulfilled') { + setCreditBalance(balanceResult.value); + } else { + log('[load] getBalance failed: %O', balanceResult.reason); + } + if (usageResult.status === 'fulfilled') { + setTeamUsage(usageResult.value); + } else { + log('[load] getTeamUsage failed: %O', usageResult.reason); + } }) .finally(() => setIsLoadingCredits(false)); }, []); diff --git a/app/src/components/settings/panels/billing/InferenceBudget.tsx b/app/src/components/settings/panels/billing/InferenceBudget.tsx index 0074e01e7f..da67d2fb6b 100644 --- a/app/src/components/settings/panels/billing/InferenceBudget.tsx +++ b/app/src/components/settings/panels/billing/InferenceBudget.tsx @@ -13,7 +13,7 @@ const InferenceBudget = ({ teamUsage, isLoadingCredits }: InferenceBudgetProps) {teamUsage && !isLoadingCredits && ( {teamUsage.cycleBudgetUsd > 0 - ? `$${teamUsage.remainingUsd.toFixed(2)} / $${teamUsage.cycleBudgetUsd.toFixed(2)} remaining` + ? `$${(teamUsage.remainingUsd ?? 0).toFixed(2)} / $${(teamUsage.cycleBudgetUsd ?? 0).toFixed(2)} remaining` : 'No recurring plan budget'} )} @@ -40,8 +40,8 @@ const InferenceBudget = ({ teamUsage, isLoadingCredits }: InferenceBudgetProps)