Files
ComfyUI_frontend/packages
Dante 07205c6e43 feat(billing): unify credits into a facade-driven CreditsTile (FE-964) (#12734)
## Summary

### AS IS 
<img width="1340" height="798" alt="Screenshot 2026-06-10 at 12 22 36
AM"
src="https://github.com/user-attachments/assets/61636fa3-e80c-427b-855b-499e1eca67da"
/>

### TO BE

<img width="1301" height="793" alt="Screenshot 2026-06-10 at 12 22 39
AM"
src="https://github.com/user-attachments/assets/62d9f5a6-da92-45df-94e7-cd3c244249f9"
/>

### Empty states ([added to DES-247 on
2026-06-11](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=3349-29750))

| 0 monthly credits | 0 credits |
| --- | --- |
| <img alt="credits-empty-monthly"
src="https://github.com/user-attachments/assets/b3c55d3b-79b0-47b1-9795-c8bf69d5efe2"
/> | <img alt="credits-empty-all"
src="https://github.com/user-attachments/assets/919081d6-64e1-483b-9c04-6b085243ebc1"
/> |

Consolidate the divergent Settings credits surfaces into one
facade-driven **CreditsTile**, implementing the DES-247 redesign so
personal and team modes always render the same balance from
`useBillingContext`.

## Changes

- **What**:
- New `CreditsTile.vue` — total + `remaining`, a stacked
monthly/additional progress bar, colored breakdown rows (`Monthly
(refills …)` / `Additional`), refresh, and a permission-gated *Add
credits* / *Upgrade to add credits* action. Owns the post-checkout
(`focus` / `pending_topup`) balance refresh.
- Extracted the duplicated inline credits card out of
`SubscriptionPanelContentWorkspace.vue` **and**
`SubscriptionPanelContentLegacy.vue` onto the shared tile.
- Replaced `LegacyCreditsPanel.vue` (read `authStore.balance` directly)
with `CreditsPanel.vue` routed through the tile; repointed
`useSettingUI` and deleted the legacy panel.
- `creditsProgress.ts` pure helper for the bar math + numeric credit
getters on `useSubscriptionCredits`.
  - i18n keys for the unified tile labels.
- DES-247 responsive variants via CSS container queries: below ~350px
tile width the `{used} used` label, `remaining` suffix, and breakdown
subtitle drop and the additional-credits value stacks under its label;
below ~230px the monthly summary compacts (`105K left of 200K`).
Additional-credits tooltip copy aligned with the updated design (per
design feedback).
- Empty states (added to DES-247 via Slack on 2026-06-11, low priority):
once the monthly allowance is depleted, an info notice renders under the
total (`Monthly credits are used up. Refills {date}` / `You're now
spending additional credits.`), the monthly bar section dims to 30%
opacity, and an `IN USE` pill marks *Additional credits*; once
everything is depleted the notice switches to `You're out of credits.
Credits refill {date}` and *Add credits* swaps to the `inverted`
(filled-white) Button variant. Gated on a loaded balance so the notice
never flashes while fetching.
- **Dependencies**: Stacked on **#12622 (FE-904 / B2)** for the facade
`tier` / `renewalDate` fields — base this PR against that branch;
retarget to `main` once FE-904 merges.

## Review Focus

- The tile reads everything from the facade (`balance.*Micros` as cents
→ credits, `subscription.tier`/`renewalDate`), so legacy and workspace
modes share one source.
- Monthly allowance still comes from `getTierCredits` (hardcoded tier
nominal). With real data the monthly *remaining* can exceed the nominal
(rolled-over credits), so the bar clamps to a full segment — same
semantics as the prior `{monthly} / {planTotal}` display; the canonical
allowance is a BE-1047 follow-up.
- `LegacyCreditsPanel` deletion: `CreditsPanel.vue` retains the
usage-history table + help links and reads the facade.

## Testing

- Unit/component (36 green): `CreditsTile.test.ts` (render, zero-state,
free-tier, permission gating, add-credits, mount+manual refresh, plus
the empty states: depletion notice copy, `IN USE` badge, `inverted`
button when fully out, no-flash-while-loading guard),
`creditsProgress.test.ts` (clamping/stacking math),
`useSubscriptionCredits.test.ts` (`*_micros`-as-cents), and
`SubscriptionPanel.test.ts` updated for the extracted tile.
- E2E (`@cloud`): `browser_tests/tests/dialogs/creditsTile.spec.ts`
boots the cloud app against mocked Firebase auth + stubbed boot
endpoints (no backend) and asserts the tile's total / progress bar /
monthly+additional breakdown / add-credits in Settings ▸ Workspace ▸
Plan & Credits, then resizes to a narrow viewport and asserts the
responsive variant (labels hidden, compacted `11K left of 21K`). A
second test boots with a drained monthly balance (0-monthly notice + `IN
USE` badge), then re-mocks a fully drained balance and refreshes the
tile in place to assert the out-of-credits state. Both pass locally
against a cloud dev server; runs in the `cloud` CI project. Drives a raw
page because the shared `comfyPage` fixture expects the OSS devtools
backend.
- Screenshot-verified the tile at the three DES-247 reference widths
(448 / 235 / 204px) against the Figma Responsiveness section — 1:1.
- Verified live in the running app (Settings ▸ Workspace ▸ Plan &
Credits) against the authenticated backend — renders 1:1 with DES-247.
The empty-state screenshots above were captured the same way
(authenticated app, real Pro subscription, balance endpoint stubbed to
the depleted values via CDP).
- `pnpm typecheck` / `typecheck:browser` / `lint` / `knip` green.

Implements FE-964 (DES-247).

---------

Co-authored-by: GitHub Action <action@github.com>
2026-06-25 04:44:59 +00:00
..