@burdenoff/microfe-billing 2026.913.1 → 2026.914.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingAdminRoutes.js +84 -71
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +90 -87
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundIssueLogsPage.js +97 -58
- package/dist/billing/modules/billing/pages/RefundIssueLogsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +22 -19
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useCheckout.js +174 -157
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +189 -182
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +293 -274
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +140 -250
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/earnings/api.js +79 -42
- package/dist/billing/modules/earnings/api.js.map +1 -1
- package/dist/billing/modules/earnings/index.js +4 -3
- package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +103 -310
- package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +545 -0
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -0
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +246 -196
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js +44 -28
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +317 -270
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +153 -150
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/hooks/useUsage.js +74 -48
- package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
- package/dist/generated/global-operations.js +220 -183
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/dist/node_modules/sonner/dist/index.js +163 -0
- package/dist/node_modules/sonner/dist/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -43,7 +43,33 @@ f`
|
|
|
43
43
|
unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)
|
|
44
44
|
}
|
|
45
45
|
`;
|
|
46
|
-
|
|
46
|
+
var _ = f`
|
|
47
|
+
query MyWorkspacesForBilling($limit: Int, $offset: Int) {
|
|
48
|
+
myWorkspaces(limit: $limit, offset: $offset) {
|
|
49
|
+
items {
|
|
50
|
+
id
|
|
51
|
+
name
|
|
52
|
+
}
|
|
53
|
+
total
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
function v() {
|
|
58
|
+
let { data: e, loading: t, error: n } = h(_, {
|
|
59
|
+
client: p("global"),
|
|
60
|
+
variables: {
|
|
61
|
+
limit: 100,
|
|
62
|
+
offset: 0
|
|
63
|
+
},
|
|
64
|
+
fetchPolicy: "cache-and-network"
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
workspaces: e?.myWorkspaces.items ?? [],
|
|
68
|
+
isLoading: t,
|
|
69
|
+
error: n ?? null
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function y(e, t) {
|
|
47
73
|
let { data: n, loading: r, error: i, refetch: a } = h(g, {
|
|
48
74
|
client: p("workspace"),
|
|
49
75
|
variables: {
|
|
@@ -60,7 +86,7 @@ function _(e, t) {
|
|
|
60
86
|
refetch: a
|
|
61
87
|
};
|
|
62
88
|
}
|
|
63
|
-
var
|
|
89
|
+
var b = f`
|
|
64
90
|
mutation ActivateSubscriptionForWorkspace(
|
|
65
91
|
$subscriptionId: ID!
|
|
66
92
|
$billingAccountId: ID!
|
|
@@ -77,8 +103,8 @@ var v = f`
|
|
|
77
103
|
}
|
|
78
104
|
}
|
|
79
105
|
`;
|
|
80
|
-
function
|
|
81
|
-
let [e, { loading: t, error: n }] = m(
|
|
106
|
+
function x() {
|
|
107
|
+
let [e, { loading: t, error: n }] = m(b, { client: p("global") });
|
|
82
108
|
return {
|
|
83
109
|
activate: async (t, n, r) => (await e({ variables: {
|
|
84
110
|
subscriptionId: t,
|
|
@@ -89,7 +115,7 @@ function y() {
|
|
|
89
115
|
error: n ?? null
|
|
90
116
|
};
|
|
91
117
|
}
|
|
92
|
-
var
|
|
118
|
+
var S = f`
|
|
93
119
|
query WorkspaceQuotaStatuses($workspaceId: ID!) {
|
|
94
120
|
workspaceQuotaStatuses(workspaceId: $workspaceId) {
|
|
95
121
|
workspaceId
|
|
@@ -101,7 +127,7 @@ var b = f`
|
|
|
101
127
|
scrapedAt
|
|
102
128
|
}
|
|
103
129
|
}
|
|
104
|
-
`,
|
|
130
|
+
`, C = f`
|
|
105
131
|
query WorkspaceQuotaHistory($workspaceId: ID!, $quotaName: String!, $limit: Int) {
|
|
106
132
|
workspaceQuotaHistory(workspaceId: $workspaceId, quotaName: $quotaName, limit: $limit) {
|
|
107
133
|
id
|
|
@@ -115,8 +141,8 @@ var b = f`
|
|
|
115
141
|
}
|
|
116
142
|
}
|
|
117
143
|
`;
|
|
118
|
-
function
|
|
119
|
-
let { data: t, loading: n, error: r } = h(
|
|
144
|
+
function w(e) {
|
|
145
|
+
let { data: t, loading: n, error: r } = h(S, {
|
|
120
146
|
client: p("workspace"),
|
|
121
147
|
variables: { workspaceId: e },
|
|
122
148
|
skip: !e,
|
|
@@ -135,8 +161,8 @@ function S(e) {
|
|
|
135
161
|
error: r ?? null
|
|
136
162
|
};
|
|
137
163
|
}
|
|
138
|
-
function
|
|
139
|
-
let { data: r, loading: i, error: a } = h(
|
|
164
|
+
function T(e, t, n = 48) {
|
|
165
|
+
let { data: r, loading: i, error: a } = h(C, {
|
|
140
166
|
client: p("workspace"),
|
|
141
167
|
variables: {
|
|
142
168
|
workspaceId: e,
|
|
@@ -152,7 +178,7 @@ function C(e, t, n = 48) {
|
|
|
152
178
|
error: a ?? null
|
|
153
179
|
};
|
|
154
180
|
}
|
|
155
|
-
function
|
|
181
|
+
function E({ billingAccountId: t, period: n = e.LAST_30_DAYS }) {
|
|
156
182
|
let { data: r, loading: i, error: a, refetch: s } = o({
|
|
157
183
|
variables: {
|
|
158
184
|
billingAccountId: t,
|
|
@@ -168,19 +194,19 @@ function w({ billingAccountId: t, period: n = e.LAST_30_DAYS }) {
|
|
|
168
194
|
refetch: s
|
|
169
195
|
};
|
|
170
196
|
}
|
|
171
|
-
function
|
|
197
|
+
function D(e, n, r = !1) {
|
|
172
198
|
return r ? t.HEALTHY : n <= 0 ? t.EXHAUSTED : e >= 90 ? t.CRITICAL : e >= 75 ? t.WARNING : t.HEALTHY;
|
|
173
199
|
}
|
|
174
|
-
var
|
|
200
|
+
var O = {
|
|
175
201
|
"agents-registered": "vibecontrols.agents.count",
|
|
176
202
|
"vibes-created": "vibecontrols.vibes.count",
|
|
177
203
|
"catalogs-created": "vibecontrols.catalogs.count",
|
|
178
204
|
"tunnels-created": "vibecontrols.tunnels.count",
|
|
179
205
|
"sessions-started": "vibecontrols.sessions.starts"
|
|
180
206
|
};
|
|
181
|
-
function
|
|
207
|
+
function k(e, t) {
|
|
182
208
|
return t.size === 0 ? e : e.map((e) => {
|
|
183
|
-
let n =
|
|
209
|
+
let n = O[e.name] ?? e.name, r = t.get(n);
|
|
184
210
|
if (!r) return e;
|
|
185
211
|
let i = r.currentValue, { totalLimit: a, noLimit: o } = e, s = o || a === 0 ? 0 : i / a * 100, c = o ? 0 : Math.max(0, a - i), l = i, u = [...e.quotaAssignments].sort((e, t) => (e.endtime ? new Date(e.endtime).getTime() : Infinity) - (t.endtime ? new Date(t.endtime).getTime() : Infinity)).map((e) => {
|
|
186
212
|
let t = Math.min(l, e.limit || 0);
|
|
@@ -194,12 +220,12 @@ function D(e, t) {
|
|
|
194
220
|
totalUsed: i,
|
|
195
221
|
usagePercentage: s,
|
|
196
222
|
isCollectorBased: !0,
|
|
197
|
-
status:
|
|
223
|
+
status: D(s, c, o),
|
|
198
224
|
quotaAssignments: u
|
|
199
225
|
};
|
|
200
226
|
});
|
|
201
227
|
}
|
|
202
|
-
function
|
|
228
|
+
function A(e) {
|
|
203
229
|
let t = /* @__PURE__ */ new Map();
|
|
204
230
|
for (let n of e) {
|
|
205
231
|
let e = t.get(n.name) || [];
|
|
@@ -207,7 +233,7 @@ function O(e) {
|
|
|
207
233
|
}
|
|
208
234
|
let n = [];
|
|
209
235
|
for (let [e, r] of t) {
|
|
210
|
-
let t = r.reduce((e, t) => e + (t.limit || 0), 0), i = r.reduce((e, t) => e + (t.used || 0), 0), a = r.reduce((e, t) => e + (t.limitLeft || 0), 0), o = t === 0 && r.every((e) => e.noLimit || (e.limit || 0) === 0), s = o || t === 0 ? 0 : i / t * 100, c =
|
|
236
|
+
let t = r.reduce((e, t) => e + (t.limit || 0), 0), i = r.reduce((e, t) => e + (t.used || 0), 0), a = r.reduce((e, t) => e + (t.limitLeft || 0), 0), o = t === 0 && r.every((e) => e.noLimit || (e.limit || 0) === 0), s = o || t === 0 ? 0 : i / t * 100, c = D(s, a, o), l = r[0]?.reusable || !1, u = r[0]?.unit || "", d = r[0]?.context, f = typeof d?.displayName == "string" ? d.displayName : void 0, p = typeof d?.description == "string" ? d.description : void 0;
|
|
211
237
|
n.push({
|
|
212
238
|
name: e,
|
|
213
239
|
displayName: f,
|
|
@@ -236,7 +262,7 @@ function O(e) {
|
|
|
236
262
|
return n === 0 ? t.usagePercentage - e.usagePercentage : n;
|
|
237
263
|
});
|
|
238
264
|
}
|
|
239
|
-
function
|
|
265
|
+
function j() {
|
|
240
266
|
let { data: e, loading: t, error: n } = r({ fetchPolicy: "cache-and-network" });
|
|
241
267
|
return {
|
|
242
268
|
billingAccounts: e?.getBillingAccountsByOrg ?? [],
|
|
@@ -244,7 +270,7 @@ function k() {
|
|
|
244
270
|
error: n ?? null
|
|
245
271
|
};
|
|
246
272
|
}
|
|
247
|
-
function
|
|
273
|
+
function M(e, t) {
|
|
248
274
|
let { data: r, loading: i, error: a, refetch: o } = n({
|
|
249
275
|
variables: {
|
|
250
276
|
billingAccountId: e,
|
|
@@ -257,15 +283,15 @@ function A(e, t) {
|
|
|
257
283
|
pooledQuotas: u(() => {
|
|
258
284
|
if (!r?.getBillingAccountQuotasSummary) return [];
|
|
259
285
|
let e = r.getBillingAccountQuotasSummary;
|
|
260
|
-
return
|
|
286
|
+
return A(e.pooledQuotas || []);
|
|
261
287
|
}, [r]),
|
|
262
288
|
isLoading: i,
|
|
263
289
|
error: a ?? null,
|
|
264
290
|
refetch: o
|
|
265
291
|
};
|
|
266
292
|
}
|
|
267
|
-
var
|
|
268
|
-
function
|
|
293
|
+
var N = 10;
|
|
294
|
+
function P(e, t = {}) {
|
|
269
295
|
let { searchQuery: n, viewMode: r = "active", productId: o } = t, s = r === "active" || r === "all", f = r === "past" || r === "all", { data: p, loading: m, error: h, refetch: g } = a({
|
|
270
296
|
variables: {
|
|
271
297
|
billingAccountId: e,
|
|
@@ -279,7 +305,7 @@ function M(e, t = {}) {
|
|
|
279
305
|
billingAccountId: e,
|
|
280
306
|
searchQuery: n || null,
|
|
281
307
|
page: 1,
|
|
282
|
-
pageSize:
|
|
308
|
+
pageSize: N,
|
|
283
309
|
productId: o || null
|
|
284
310
|
},
|
|
285
311
|
skip: !e || !f,
|
|
@@ -311,13 +337,13 @@ function M(e, t = {}) {
|
|
|
311
337
|
createdAt: e.createdAt,
|
|
312
338
|
planName: e.planName ?? void 0,
|
|
313
339
|
productId: e.productId ?? void 0,
|
|
314
|
-
aggregatedQuotas:
|
|
340
|
+
aggregatedQuotas: A(t)
|
|
315
341
|
};
|
|
316
|
-
}, []), E = u(() => p?.getSubscriptionUsageOverview ? p.getSubscriptionUsageOverview.map(T) : [], [p, T]), D = u(() => _?.getSubscriptionUsageOverview ? (_.getSubscriptionUsageOverview.subscriptions || _.getSubscriptionUsageOverview).map(T) : [], [_, T]),
|
|
342
|
+
}, []), E = u(() => p?.getSubscriptionUsageOverview ? p.getSubscriptionUsageOverview.map(T) : [], [p, T]), D = u(() => _?.getSubscriptionUsageOverview ? (_.getSubscriptionUsageOverview.subscriptions || _.getSubscriptionUsageOverview).map(T) : [], [_, T]), O = u(() => {
|
|
317
343
|
let e = _?.getSubscriptionUsageOverview;
|
|
318
344
|
return e ? e.totalCount === void 0 ? {
|
|
319
345
|
totalCount: D.length,
|
|
320
|
-
hasMore: D.length >=
|
|
346
|
+
hasMore: D.length >= N * C
|
|
321
347
|
} : {
|
|
322
348
|
totalCount: e.totalCount,
|
|
323
349
|
hasMore: D.length < e.totalCount
|
|
@@ -333,15 +359,15 @@ function M(e, t = {}) {
|
|
|
333
359
|
l(() => {
|
|
334
360
|
w(1);
|
|
335
361
|
}, [n, r]);
|
|
336
|
-
let
|
|
337
|
-
if (!b || x || !
|
|
362
|
+
let k = c(async () => {
|
|
363
|
+
if (!b || x || !O.hasMore) return;
|
|
338
364
|
S(!0);
|
|
339
365
|
let e = C + 1;
|
|
340
366
|
try {
|
|
341
367
|
await b({
|
|
342
368
|
variables: {
|
|
343
369
|
page: e,
|
|
344
|
-
pageSize:
|
|
370
|
+
pageSize: N
|
|
345
371
|
},
|
|
346
372
|
updateQuery: (e, { fetchMoreResult: t }) => {
|
|
347
373
|
if (!t) return e;
|
|
@@ -357,9 +383,9 @@ function M(e, t = {}) {
|
|
|
357
383
|
}, [
|
|
358
384
|
b,
|
|
359
385
|
x,
|
|
360
|
-
|
|
386
|
+
O.hasMore,
|
|
361
387
|
C
|
|
362
|
-
]),
|
|
388
|
+
]), j = c(() => {
|
|
363
389
|
w(1), s && g(), f && y();
|
|
364
390
|
}, [
|
|
365
391
|
s,
|
|
@@ -373,14 +399,14 @@ function M(e, t = {}) {
|
|
|
373
399
|
isLoading: s ? m : !1,
|
|
374
400
|
isPastSubscriptionsLoading: f ? v : !1,
|
|
375
401
|
error: h ?? null,
|
|
376
|
-
refetch:
|
|
377
|
-
loadMorePastSubscriptions:
|
|
378
|
-
hasMorePastSubscriptions:
|
|
402
|
+
refetch: j,
|
|
403
|
+
loadMorePastSubscriptions: k,
|
|
404
|
+
hasMorePastSubscriptions: O.hasMore,
|
|
379
405
|
isLoadingMorePast: x,
|
|
380
|
-
pastSubscriptionsCount:
|
|
406
|
+
pastSubscriptionsCount: O.totalCount
|
|
381
407
|
};
|
|
382
408
|
}
|
|
383
|
-
function
|
|
409
|
+
function F(e, t, n, r) {
|
|
384
410
|
return {
|
|
385
411
|
filters: {
|
|
386
412
|
quotaAssignmentId: e,
|
|
@@ -392,7 +418,7 @@ function N(e, t, n, r) {
|
|
|
392
418
|
}
|
|
393
419
|
};
|
|
394
420
|
}
|
|
395
|
-
function
|
|
421
|
+
function I(e) {
|
|
396
422
|
return {
|
|
397
423
|
id: e.id,
|
|
398
424
|
amount: e.amount,
|
|
@@ -404,15 +430,15 @@ function P(e) {
|
|
|
404
430
|
lastUpdated: e.createdAt
|
|
405
431
|
};
|
|
406
432
|
}
|
|
407
|
-
function
|
|
433
|
+
function L(e, t = 20, n = null, r) {
|
|
408
434
|
let { data: i, loading: a, error: o, refetch: c } = s({
|
|
409
435
|
client: p("workspace"),
|
|
410
|
-
variables:
|
|
436
|
+
variables: F(e, t, n, r),
|
|
411
437
|
skip: !e || !r,
|
|
412
438
|
fetchPolicy: "cache-and-network"
|
|
413
439
|
});
|
|
414
440
|
return {
|
|
415
|
-
usages: (i?.getUsageRecords?.items ?? []).map(
|
|
441
|
+
usages: (i?.getUsageRecords?.items ?? []).map(I),
|
|
416
442
|
hasNextPage: i?.getUsageRecords?.hasNextPage ?? !1,
|
|
417
443
|
nextCursor: i?.getUsageRecords?.nextCursor ?? null,
|
|
418
444
|
isLoading: a,
|
|
@@ -420,20 +446,20 @@ function F(e, t = 20, n = null, r) {
|
|
|
420
446
|
refetch: c
|
|
421
447
|
};
|
|
422
448
|
}
|
|
423
|
-
function
|
|
424
|
-
let s =
|
|
449
|
+
function R({ billingAccountId: t, workspaceId: n, productId: r, period: i = e.LAST_30_DAYS, searchQuery: a, viewMode: o = "active" }) {
|
|
450
|
+
let s = E({
|
|
425
451
|
billingAccountId: t,
|
|
426
452
|
period: i
|
|
427
|
-
}), l =
|
|
453
|
+
}), l = M(t), { statusMap: d } = w(n), f = P(t, {
|
|
428
454
|
searchQuery: a,
|
|
429
455
|
viewMode: o,
|
|
430
456
|
productId: r
|
|
431
|
-
}), p = u(() =>
|
|
457
|
+
}), p = u(() => k(l.pooledQuotas, d), [l.pooledQuotas, d]), m = u(() => f.activeSubscriptions.map((e) => ({
|
|
432
458
|
...e,
|
|
433
|
-
aggregatedQuotas:
|
|
459
|
+
aggregatedQuotas: k(e.aggregatedQuotas, d)
|
|
434
460
|
})), [f.activeSubscriptions, d]), h = u(() => f.pastSubscriptions.map((e) => ({
|
|
435
461
|
...e,
|
|
436
|
-
aggregatedQuotas:
|
|
462
|
+
aggregatedQuotas: k(e.aggregatedQuotas, d)
|
|
437
463
|
})), [f.pastSubscriptions, d]), g = c(() => {
|
|
438
464
|
s.refetch(), l.refetch(), f.refetch();
|
|
439
465
|
}, [
|
|
@@ -457,6 +483,6 @@ function I({ billingAccountId: t, workspaceId: n, productId: r, period: i = e.LA
|
|
|
457
483
|
};
|
|
458
484
|
}
|
|
459
485
|
//#endregion
|
|
460
|
-
export {
|
|
486
|
+
export { x as useActivateSubscriptionForWorkspace, M as useBillingAccountQuotasSummary, j as useBillingAccountsForUsage, R as useEnhancedUsagePage, v as useMyWorkspaces, L as useQuotaUsageDetails, P as useSubscriptionsWithQuotas, E as useUsageDashboard, T as useWorkspaceQuotaHistory, y as useWorkspaceQuotaOverview };
|
|
461
487
|
|
|
462
488
|
//# sourceMappingURL=useUsage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUsage.js","names":[],"sources":["../../../../../src/billing/modules/usage/hooks/useUsage.ts"],"sourcesContent":["/**\n * Usage Module - Hooks\n * Custom hooks for usage data fetching and mutations\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react';\nimport {\n useGetUsageDashboardQuery,\n useGetBillingAccountQuotasSummaryForUsageQuery,\n useGetSubscriptionsWithQuotasQuery,\n useGetPastSubscriptionsWithQuotasQuery,\n useGetBillingAccountsForUsageQuery,\n useGetQuotaAnalyticsForUsageQuery,\n type GetPastSubscriptionsWithQuotasQuery,\n} from '../../../../generated/global-operations';\nimport {\n useGetUsageRecordsQuery,\n type GetUsageRecordsQuery,\n type GetUsageRecordsQueryVariables,\n} from '../../../../generated/wspace-operations';\nimport { useGatewayClient, useQuery, useMutation, gql } from '@burdenoff/fe-libs/shared/graphql';\nimport { AnalyticsPeriod, QuotaStatus } from '../../../shared/types';\nimport type { UsageDashboard } from '../../../shared/types';\n\n// ============================================================================\n// Workspace Quota Assignment — managed by activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_OVERVIEW_QUERY = gql`\n query WorkspaceQuotaOverview($workspaceId: ID!, $productId: ID) {\n workspaceQuotaOverview(workspaceId: $workspaceId, productId: $productId) {\n assignment {\n id\n workspaceId\n quotaName\n productId\n quotaMode\n billingAccountId\n subscriptionId\n limit\n used\n noLimit\n resetPeriod\n lastResetAt\n isActive\n createdAt\n }\n liveStatus {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n effectiveUsed\n remaining\n usagePercentage\n }\n }\n`;\n\nconst UNASSIGN_WORKSPACE_QUOTA_MUTATION = gql`\n mutation UnassignWorkspaceQuota($id: ID!, $workspaceId: ID!) {\n unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)\n }\n`;\n\nexport interface WorkspaceQuotaAssignmentItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode: 'dedicated' | 'pooled';\n billingAccountId: string | null;\n subscriptionId: string | null;\n limit: number | null;\n used: number;\n noLimit: boolean;\n resetPeriod: string;\n lastResetAt: string | null;\n isActive: boolean;\n createdAt: string;\n}\n\nexport interface WorkspaceQuotaOverviewItem {\n assignment: WorkspaceQuotaAssignmentItem;\n liveStatus: WorkspaceQuotaStatusItem | null;\n effectiveUsed: number;\n remaining: number | null;\n usagePercentage: number;\n}\n\nexport interface AssignWorkspaceQuotaInput {\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode?: 'dedicated' | 'pooled';\n billingAccountId?: string;\n subscriptionId?: string;\n limit?: number;\n noLimit?: boolean;\n resetPeriod?: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface UseWorkspaceQuotaOverviewResult {\n overview: WorkspaceQuotaOverviewItem[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useWorkspaceQuotaOverview(\n workspaceId?: string,\n productId?: string\n): UseWorkspaceQuotaOverviewResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useQuery<{\n workspaceQuotaOverview: WorkspaceQuotaOverviewItem[];\n }>(WORKSPACE_QUOTA_OVERVIEW_QUERY, {\n client: wsClient,\n variables: { workspaceId, productId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n overview: data?.workspaceQuotaOverview ?? [],\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// activateSubscriptionForWorkspace — single backend call that assigns all\n// WORKSPACE-scoped quotas from the purchased plan. Replaces the N individual\n// assignWorkspaceQuota calls so the UI never sends quota names/limits to the server.\n// ============================================================================\n\nconst ACTIVATE_SUBSCRIPTION_FOR_WORKSPACE_MUTATION = gql`\n mutation ActivateSubscriptionForWorkspace(\n $subscriptionId: ID!\n $billingAccountId: ID!\n $workspaceId: ID!\n ) {\n activateSubscriptionForWorkspace(\n subscriptionId: $subscriptionId\n billingAccountId: $billingAccountId\n workspaceId: $workspaceId\n ) {\n workspaceId\n assignedQuotas\n skippedQuotas\n }\n }\n`;\n\nexport interface ActivateSubscriptionForWorkspaceResult {\n workspaceId: string;\n assignedQuotas: number;\n skippedQuotas: number;\n}\n\ninterface UseActivateSubscriptionForWorkspaceResult {\n activate: (\n subscriptionId: string,\n billingAccountId: string,\n workspaceId: string\n ) => Promise<ActivateSubscriptionForWorkspaceResult | null>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useActivateSubscriptionForWorkspace(): UseActivateSubscriptionForWorkspaceResult {\n const globalClient = useGatewayClient('global');\n const [mutate, { loading, error }] = useMutation<\n { activateSubscriptionForWorkspace: ActivateSubscriptionForWorkspaceResult },\n { subscriptionId: string; billingAccountId: string; workspaceId: string }\n >(ACTIVATE_SUBSCRIPTION_FOR_WORKSPACE_MUTATION, { client: globalClient });\n\n const activate = async (\n subscriptionId: string,\n billingAccountId: string,\n workspaceId: string\n ) => {\n const result = await mutate({ variables: { subscriptionId, billingAccountId, workspaceId } });\n return result.data?.activateSubscriptionForWorkspace ?? null;\n };\n\n return { activate, isLoading: loading, error: error ?? null };\n}\n\ninterface UseUnassignWorkspaceQuotaResult {\n unassign: (id: string, workspaceId: string) => Promise<boolean>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useUnassignWorkspaceQuota(): UseUnassignWorkspaceQuotaResult {\n const wsClient = useGatewayClient('workspace');\n const [mutate, { loading, error }] = useMutation<\n { unassignWorkspaceQuota: boolean },\n { id: string; workspaceId: string }\n >(UNASSIGN_WORKSPACE_QUOTA_MUTATION, { client: wsClient });\n\n const unassign = async (id: string, workspaceId: string) => {\n const result = await mutate({ variables: { id, workspaceId } });\n return result.data?.unassignWorkspaceQuota ?? false;\n };\n\n return { unassign, isLoading: loading, error: error ?? null };\n}\n\n// ============================================================================\n// Workspace Quota Status — live values from activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_STATUSES_QUERY = gql`\n query WorkspaceQuotaStatuses($workspaceId: ID!) {\n workspaceQuotaStatuses(workspaceId: $workspaceId) {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n }\n`;\n\nconst WORKSPACE_QUOTA_HISTORY_QUERY = gql`\n query WorkspaceQuotaHistory($workspaceId: ID!, $quotaName: String!, $limit: Int) {\n workspaceQuotaHistory(workspaceId: $workspaceId, quotaName: $quotaName, limit: $limit) {\n id\n workspaceId\n quotaName\n productId\n value\n limit\n noLimit\n snapshotAt\n }\n }\n`;\n\nexport interface WorkspaceQuotaStatusItem {\n workspaceId: string;\n quotaName: string;\n productId: string;\n currentValue: number;\n limit: number | null;\n noLimit: boolean;\n scrapedAt: string;\n}\n\nexport interface WorkspaceQuotaSnapshotItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n value: number;\n limit: number | null;\n noLimit: boolean;\n snapshotAt: string;\n}\n\ninterface UseWorkspaceQuotaStatusesResult {\n statuses: WorkspaceQuotaStatusItem[];\n statusMap: Map<string, WorkspaceQuotaStatusItem>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaStatuses(workspaceId?: string): UseWorkspaceQuotaStatusesResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{ workspaceQuotaStatuses: WorkspaceQuotaStatusItem[] }>(\n WORKSPACE_QUOTA_STATUSES_QUERY,\n {\n client: wsClient,\n variables: { workspaceId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n }\n );\n\n const { statuses, statusMap } = useMemo(() => {\n const list = data?.workspaceQuotaStatuses ?? [];\n return { statuses: list, statusMap: new Map(list.map((s) => [s.quotaName, s])) };\n }, [data]);\n\n return { statuses, statusMap, isLoading: loading, error: error ?? null };\n}\n\ninterface UseWorkspaceQuotaHistoryResult {\n snapshots: WorkspaceQuotaSnapshotItem[];\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaHistory(\n workspaceId?: string,\n quotaName?: string,\n limit: number = 48\n): UseWorkspaceQuotaHistoryResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{\n workspaceQuotaHistory: WorkspaceQuotaSnapshotItem[];\n }>(WORKSPACE_QUOTA_HISTORY_QUERY, {\n client: wsClient,\n variables: { workspaceId, quotaName, limit },\n skip: !workspaceId || !quotaName,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n snapshots: data?.workspaceQuotaHistory ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n// ============================================================================\n// Dashboard Hook\n// ============================================================================\n\ninterface UseUsageDashboardParams {\n billingAccountId?: string;\n period?: AnalyticsPeriod;\n}\n\ninterface UseUsageDashboardResult {\n dashboard: UsageDashboard | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useUsageDashboard({\n billingAccountId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n}: UseUsageDashboardParams): UseUsageDashboardResult {\n const { data, loading, error, refetch } = useGetUsageDashboardQuery({\n variables: { billingAccountId: billingAccountId!, period },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n dashboard: data?.getUsageDashboard ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// Enhanced Usage Page Hooks\n// ============================================================================\n\n/**\n * Quota assignment from the API\n */\ninterface QuotaAssignmentSummary {\n id: string;\n name: string;\n unit: string;\n quotaType: 'POOLED' | 'PRODUCT_SPECIFIC';\n reusable: boolean;\n limit: number;\n limitLeft: number;\n used: number;\n usagePercentage: number;\n noLimit: boolean;\n subscriptionId?: string;\n context?: Record<string, unknown> | null;\n recentUsages: Array<{\n id: string;\n amount: number;\n description: string;\n timestamp: string;\n tags?: Record<string, unknown>;\n }>;\n createdAt: string;\n endtime: string;\n}\n\n/**\n * Aggregated quota for display (multiple quota assignments with same name combined)\n */\nexport interface AggregatedQuota {\n name: string;\n displayName?: string;\n description?: string;\n unit: string;\n totalLimit: number;\n totalUsed: number;\n usagePercentage: number;\n status: QuotaStatus;\n quotaCount: number;\n noLimit: boolean;\n reusable: boolean;\n /** True when usage comes from the activity-svc scraper, not billing recordUsage events. */\n isCollectorBased: boolean;\n quotaAssignments: QuotaAssignmentSummary[];\n}\n\n/**\n * Subscription with aggregated quotas\n */\nexport interface SubscriptionWithAggregatedQuotas {\n id: string;\n status: string;\n planName?: string;\n productId?: string;\n createdAt: string;\n aggregatedQuotas: AggregatedQuota[];\n}\n\n/**\n * Helper function to calculate quota status based on usage percentage\n */\nfunction calculateQuotaStatus(\n usagePercentage: number,\n limitLeft: number,\n noLimit = false\n): QuotaStatus {\n if (noLimit) return QuotaStatus.HEALTHY;\n if (limitLeft <= 0) return QuotaStatus.EXHAUSTED;\n if (usagePercentage >= 90) return QuotaStatus.CRITICAL;\n if (usagePercentage >= 75) return QuotaStatus.WARNING;\n return QuotaStatus.HEALTHY;\n}\n\n// Maps billing quota names to their corresponding scraper metric names in workspace_quota_statuses.\n// Scraper keys use dot-notation product prefix; billing keys use kebab-case.\nconst BILLING_TO_SCRAPER_METRIC: Record<string, string> = {\n 'agents-registered': 'vibecontrols.agents.count',\n 'vibes-created': 'vibecontrols.vibes.count',\n 'catalogs-created': 'vibecontrols.catalogs.count',\n 'tunnels-created': 'vibecontrols.tunnels.count',\n 'sessions-started': 'vibecontrols.sessions.starts',\n};\n\n/**\n * Merges live collector values from activity-svc into a list of aggregated quotas.\n * Overrides `totalUsed` (and derived fields) when a live value exists, and distributes\n * the live total across individual assignments (earliest-expiry first) so the\n * Quota Breakdown shows real per-assignment consumption instead of always-0.\n */\nfunction mergeLiveValues(\n aggregated: AggregatedQuota[],\n statusMap: Map<string, WorkspaceQuotaStatusItem>\n): AggregatedQuota[] {\n if (statusMap.size === 0) return aggregated;\n return aggregated.map((q) => {\n const scraperKey = BILLING_TO_SCRAPER_METRIC[q.name] ?? q.name;\n const live = statusMap.get(scraperKey);\n if (!live) return q;\n\n const totalUsed = live.currentValue;\n const { totalLimit, noLimit } = q;\n const usagePercentage = noLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n const limitLeft = noLimit ? 0 : Math.max(0, totalLimit - totalUsed);\n\n // Distribute live total into assignments: fill earliest-expiry first, overflow to next.\n let remaining = totalUsed;\n const updatedAssignments = [...q.quotaAssignments]\n .sort((a, b) => {\n const dateA = a.endtime ? new Date(a.endtime).getTime() : Infinity;\n const dateB = b.endtime ? new Date(b.endtime).getTime() : Infinity;\n return dateA - dateB;\n })\n .map((a) => {\n const assignmentUsed = Math.min(remaining, a.limit || 0);\n remaining = Math.max(0, remaining - assignmentUsed);\n return { ...a, used: assignmentUsed };\n });\n\n return {\n ...q,\n totalUsed,\n usagePercentage,\n isCollectorBased: true,\n status: calculateQuotaStatus(usagePercentage, limitLeft, noLimit),\n quotaAssignments: updatedAssignments,\n };\n });\n}\n\n/**\n * Helper function to aggregate quotas by name\n */\nfunction aggregateQuotasByName(quotas: QuotaAssignmentSummary[]): AggregatedQuota[] {\n const grouped = new Map<string, QuotaAssignmentSummary[]>();\n\n for (const quota of quotas) {\n const existing = grouped.get(quota.name) || [];\n existing.push(quota);\n grouped.set(quota.name, existing);\n }\n\n const aggregated: AggregatedQuota[] = [];\n\n for (const [name, assignments] of grouped) {\n // Sum up limits and usage first\n const totalLimit = assignments.reduce((sum, q) => sum + (q.limit || 0), 0);\n const totalUsed = assignments.reduce((sum, q) => sum + (q.used || 0), 0);\n const totalLimitLeft = assignments.reduce((sum, q) => sum + (q.limitLeft || 0), 0);\n\n // Check if there's actually no limit (ignore noLimit flag if there's a numeric limit)\n // Only treat as unlimited if totalLimit is 0 or all assignments explicitly have noLimit AND no numeric limit\n const hasNoLimit =\n totalLimit === 0 && assignments.every((q) => q.noLimit || (q.limit || 0) === 0);\n\n // Calculate percentage\n const usagePercentage = hasNoLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n\n // Get status\n const status = calculateQuotaStatus(usagePercentage, totalLimitLeft, hasNoLimit);\n\n // Get reusable, unit, and display info from first assignment\n const reusable = assignments[0]?.reusable || false;\n const unit = assignments[0]?.unit || '';\n const ctx = assignments[0]?.context;\n const displayName = typeof ctx?.displayName === 'string' ? ctx.displayName : undefined;\n const description = typeof ctx?.description === 'string' ? ctx.description : undefined;\n\n aggregated.push({\n name,\n displayName,\n description,\n unit,\n totalLimit: hasNoLimit ? 0 : totalLimit,\n totalUsed,\n usagePercentage,\n status,\n quotaCount: assignments.length,\n noLimit: hasNoLimit,\n isCollectorBased: false,\n reusable,\n quotaAssignments: assignments,\n });\n }\n\n // Sort by status priority (critical first) then by usage percentage\n const statusPriority: Record<QuotaStatus, number> = {\n EXHAUSTED: 0,\n CRITICAL: 1,\n WARNING: 2,\n HEALTHY: 3,\n INACTIVE: 4,\n };\n\n return aggregated.sort((a, b) => {\n const priorityDiff = statusPriority[a.status] - statusPriority[b.status];\n if (priorityDiff !== 0) return priorityDiff;\n return b.usagePercentage - a.usagePercentage;\n });\n}\n\n/**\n * Hook to get billing accounts for the selector\n */\ninterface UseBillingAccountsForUsageResult {\n billingAccounts: Array<{\n id: string;\n name: string;\n email: string;\n currency: string;\n creditAmount: number;\n isActive: boolean;\n isDefault: boolean;\n }>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useBillingAccountsForUsage(): UseBillingAccountsForUsageResult {\n const { data, loading, error } = useGetBillingAccountsForUsageQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n billingAccounts: data?.getBillingAccountsByOrg ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to get billing account quotas summary with aggregation\n * Only returns pooled quotas - subscription quotas come from useSubscriptionsWithQuotas\n */\ninterface UseBillingAccountQuotasSummaryResult {\n pooledQuotas: AggregatedQuota[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useBillingAccountQuotasSummary(\n billingAccountId: string | undefined,\n productId?: string\n): UseBillingAccountQuotasSummaryResult {\n const { data, loading, error, refetch } = useGetBillingAccountQuotasSummaryForUsageQuery({\n variables: { billingAccountId: billingAccountId!, productId },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n const pooledQuotas = useMemo(() => {\n if (!data?.getBillingAccountQuotasSummary) {\n return [];\n }\n\n const summary = data.getBillingAccountQuotasSummary;\n return aggregateQuotasByName(summary.pooledQuotas || []);\n }, [data]);\n\n return {\n pooledQuotas,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Subscription from the API\n */\ninterface SubscriptionQuota {\n id: string;\n name: string;\n limits: { type: string; value: number; unit?: string; originalValue?: number };\n context?: Record<string, unknown> | null;\n currentUsageSum: number;\n usageCount: number;\n createdAt: string;\n endtime: string;\n}\n\ninterface SubscriptionFromAPI {\n id: string;\n status: string;\n createdAt: string;\n planName?: string | null;\n productId?: string | null;\n quotas: SubscriptionQuota[];\n}\n\n/**\n * Hook to get subscriptions with their quotas grouped by status\n * Uses lazy loading for past subscriptions and supports backend search\n */\ntype ViewMode = 'active' | 'past' | 'all';\n\nconst PAST_SUBSCRIPTIONS_PAGE_SIZE = 10;\n\ninterface UseSubscriptionsWithQuotasOptions {\n searchQuery?: string;\n viewMode?: ViewMode;\n productId?: string;\n}\n\ninterface UseSubscriptionsWithQuotasResult {\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n refetch: () => void;\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useSubscriptionsWithQuotas(\n billingAccountId: string | undefined,\n options: UseSubscriptionsWithQuotasOptions = {}\n): UseSubscriptionsWithQuotasResult {\n const { searchQuery, viewMode = 'active', productId } = options;\n\n // Determine which subscriptions to fetch based on viewMode\n const shouldFetchActive = viewMode === 'active' || viewMode === 'all';\n const shouldFetchPast = viewMode === 'past' || viewMode === 'all';\n\n // Query for active subscriptions\n const { data, loading, error, refetch } = useGetSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n productId: productId || null,\n },\n skip: !billingAccountId || !shouldFetchActive,\n fetchPolicy: 'cache-and-network',\n });\n\n // Query for past subscriptions with pagination - only when viewMode is 'past' or 'all'\n const {\n data: pastData,\n loading: pastLoading,\n refetch: refetchPast,\n fetchMore: fetchMorePast,\n } = useGetPastSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n page: 1,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n productId: productId || null,\n },\n skip: !billingAccountId || !shouldFetchPast,\n fetchPolicy: 'cache-and-network',\n });\n\n // Track if we're loading more past subscriptions\n const [isLoadingMorePast, setIsLoadingMorePast] = useState(false);\n const [currentPastPage, setCurrentPastPage] = useState(1);\n\n // Helper function to process subscription data\n const processSubscription = useCallback(\n (sub: SubscriptionFromAPI): SubscriptionWithAggregatedQuotas => {\n const quotaSummaries: QuotaAssignmentSummary[] = (sub.quotas || []).map((q) => {\n const limit = q.limits?.value || 0;\n const used = q.currentUsageSum || 0;\n const limitLeft = Math.max(0, limit - used);\n const usagePercentage = limit > 0 ? (used / limit) * 100 : 0;\n const unit = q.limits?.unit || '';\n\n return {\n id: q.id,\n name: q.name,\n unit,\n quotaType: 'PRODUCT_SPECIFIC' as const,\n reusable: false,\n limit,\n limitLeft,\n used,\n usagePercentage,\n noLimit: q.limits?.type !== 'number' && q.limits?.type !== 'numeric',\n subscriptionId: sub.id,\n context: q.context,\n recentUsages: [],\n createdAt: q.createdAt,\n endtime: q.endtime,\n };\n });\n\n return {\n id: sub.id,\n status: sub.status,\n createdAt: sub.createdAt,\n planName: sub.planName ?? undefined,\n productId: sub.productId ?? undefined,\n aggregatedQuotas: aggregateQuotasByName(quotaSummaries),\n };\n },\n []\n );\n\n // Process active subscriptions\n const activeSubscriptions = useMemo(() => {\n if (!data?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] = data.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [data, processSubscription]);\n\n // Process past subscriptions\n const pastSubscriptions = useMemo(() => {\n if (!pastData?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] =\n pastData.getSubscriptionUsageOverview.subscriptions || pastData.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [pastData, processSubscription]);\n\n // Get pagination info for past subscriptions\n const pastSubscriptionsPagination = useMemo(() => {\n const overview = pastData?.getSubscriptionUsageOverview;\n if (!overview) {\n return { totalCount: 0, hasMore: false };\n }\n // If it's the new paginated format\n if (overview.totalCount !== undefined) {\n return {\n totalCount: overview.totalCount,\n hasMore: pastSubscriptions.length < overview.totalCount,\n };\n }\n // Legacy format - assume there might be more if we got a full page\n return {\n totalCount: pastSubscriptions.length,\n hasMore: pastSubscriptions.length >= PAST_SUBSCRIPTIONS_PAGE_SIZE * currentPastPage,\n };\n }, [pastData, pastSubscriptions.length, currentPastPage]);\n\n // Reset page when search query or viewMode changes\n useEffect(() => {\n setCurrentPastPage(1);\n }, [searchQuery, viewMode]);\n\n // Load more past subscriptions\n const loadMorePastSubscriptions = useCallback(async () => {\n if (!fetchMorePast || isLoadingMorePast || !pastSubscriptionsPagination.hasMore) return;\n\n setIsLoadingMorePast(true);\n const nextPage = currentPastPage + 1;\n\n try {\n await fetchMorePast({\n variables: {\n page: nextPage,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n },\n updateQuery: (\n prev: GetPastSubscriptionsWithQuotasQuery,\n { fetchMoreResult }: { fetchMoreResult: GetPastSubscriptionsWithQuotasQuery }\n ) => {\n if (!fetchMoreResult) return prev;\n\n const prevSubs = prev?.getSubscriptionUsageOverview ?? [];\n const newSubs = fetchMoreResult?.getSubscriptionUsageOverview ?? [];\n\n return {\n getSubscriptionUsageOverview: [...prevSubs, ...newSubs],\n };\n },\n });\n setCurrentPastPage(nextPage);\n } catch (err) {\n console.error('Failed to load more past subscriptions:', err);\n } finally {\n setIsLoadingMorePast(false);\n }\n }, [fetchMorePast, isLoadingMorePast, pastSubscriptionsPagination.hasMore, currentPastPage]);\n\n const combinedRefetch = useCallback(() => {\n setCurrentPastPage(1);\n if (shouldFetchActive) refetch();\n if (shouldFetchPast) refetchPast();\n }, [shouldFetchActive, shouldFetchPast, refetch, refetchPast]);\n\n return {\n activeSubscriptions,\n pastSubscriptions,\n isLoading: shouldFetchActive ? loading : false,\n isPastSubscriptionsLoading: shouldFetchPast ? pastLoading : false,\n error: error ?? null,\n refetch: combinedRefetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions,\n hasMorePastSubscriptions: pastSubscriptionsPagination.hasMore,\n isLoadingMorePast,\n pastSubscriptionsCount: pastSubscriptionsPagination.totalCount,\n };\n}\n\n/**\n * Hook to get quota usage details for drill-down using cursor-based pagination.\n *\n * The activity-svc API is cursor-based (limit / cursor → items / hasNextPage / nextCursor).\n * Callers should maintain a cursor stack:\n * - push nextCursor to go forward\n * - pop to go back\n * - pass null as cursor for the first page\n */\nexport interface QuotaUsageDetail {\n id: string;\n amount: number;\n description: string;\n tags?: Record<string, unknown>;\n productId?: string;\n userId?: string;\n timestamp: string;\n lastUpdated?: string;\n}\n\ninterface UseQuotaUsageDetailsResult {\n usages: QuotaUsageDetail[];\n hasNextPage: boolean;\n nextCursor: string | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\ntype UsageRecord = GetUsageRecordsQuery['getUsageRecords']['items'][number];\n\nexport function buildUsageRecordsVariables(\n quotaAssignmentId: string | undefined,\n pageSize: number,\n cursor: string | null,\n workspaceId?: string\n): GetUsageRecordsQueryVariables {\n return {\n filters: {\n quotaAssignmentId,\n workspaceId,\n },\n pagination: {\n limit: pageSize,\n ...(cursor ? { cursor } : {}),\n },\n };\n}\n\nexport function mapUsageRecordToQuotaUsageDetail(record: UsageRecord): QuotaUsageDetail {\n return {\n id: record.id,\n amount: record.amount,\n description: record.description ?? '',\n tags: (record.metadata as Record<string, unknown>) ?? {},\n productId: record.productId,\n userId: record.userId ?? undefined,\n timestamp: record.createdAt,\n lastUpdated: record.createdAt,\n };\n}\n\nexport function useQuotaUsageDetails(\n quotaAssignmentId: string | undefined,\n pageSize: number = 20,\n cursor: string | null = null,\n workspaceId?: string\n): UseQuotaUsageDetailsResult {\n // Query workspace gateway directly — usage records live in wspace-activity-svc.\n // Uses the dedicated workspace Apollo client so it doesn't conflict with the\n // shell's global Apollo client used by all other billing queries.\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useGetUsageRecordsQuery({\n client: wsClient,\n variables: buildUsageRecordsVariables(quotaAssignmentId, pageSize, cursor, workspaceId),\n skip: !quotaAssignmentId || !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const items: GetUsageRecordsQuery['getUsageRecords']['items'] =\n data?.getUsageRecords?.items ?? [];\n\n return {\n usages: items.map(mapUsageRecordToQuotaUsageDetail),\n hasNextPage: data?.getUsageRecords?.hasNextPage ?? false,\n nextCursor: data?.getUsageRecords?.nextCursor ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to get quota analytics\n */\ninterface UseQuotaAnalyticsResult {\n analytics: {\n dailyUsage: Array<{ date: string; usage: number; count: number }>;\n usageByType: Array<{ resourceType: string; usage: number; count: number; percentage: number }>;\n trends?: {\n growthRate?: number;\n predictedUsage?: number;\n averageDailyUsage?: number;\n };\n peakUsage?: {\n maxDailyUsage: number;\n peakDate: string;\n peakHour?: number;\n };\n } | null;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useQuotaAnalytics(\n quotaAssignmentId: string | undefined,\n period: AnalyticsPeriod = AnalyticsPeriod.LAST_30_DAYS\n): UseQuotaAnalyticsResult {\n const { data, loading, error } = useGetQuotaAnalyticsForUsageQuery({\n variables: { quotaAssignmentId: quotaAssignmentId!, period },\n skip: !quotaAssignmentId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n analytics: data?.getQuotaAnalytics ?? null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Combined hook for the enhanced usage page\n */\ninterface UseEnhancedUsagePageParams {\n billingAccountId: string | undefined;\n workspaceId?: string;\n productId?: string;\n period?: AnalyticsPeriod;\n searchQuery?: string;\n viewMode?: ViewMode;\n}\n\ninterface UseEnhancedUsagePageResult {\n // Dashboard overview\n dashboard: UsageDashboard | null;\n\n // Pooled quotas (subscription quotas come from subscriptions directly)\n pooledQuotas: AggregatedQuota[];\n\n // Subscriptions with their quotas\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n\n // Loading states\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n\n // Actions\n refetch: () => void;\n\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useEnhancedUsagePage({\n billingAccountId,\n workspaceId,\n productId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n searchQuery,\n viewMode = 'active',\n}: UseEnhancedUsagePageParams): UseEnhancedUsagePageResult {\n // Dashboard for overview stats\n const dashboardResult = useUsageDashboard({\n billingAccountId,\n period,\n });\n\n // Quotas summary with aggregation\n const quotasSummaryResult = useBillingAccountQuotasSummary(billingAccountId);\n\n // Live collector values from activity-svc (source of truth for count/concurrent quotas)\n const { statusMap } = useWorkspaceQuotaStatuses(workspaceId);\n\n // Subscriptions with quotas - passes searchQuery and viewMode to backend\n const subscriptionsResult = useSubscriptionsWithQuotas(billingAccountId, {\n searchQuery,\n viewMode,\n productId,\n });\n\n // Merge live values into both pooled quotas and subscription quotas\n const pooledQuotas = useMemo(\n () => mergeLiveValues(quotasSummaryResult.pooledQuotas, statusMap),\n [quotasSummaryResult.pooledQuotas, statusMap]\n );\n\n const activeSubscriptions = useMemo(\n () =>\n subscriptionsResult.activeSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.activeSubscriptions, statusMap]\n );\n\n const pastSubscriptions = useMemo(\n () =>\n subscriptionsResult.pastSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.pastSubscriptions, statusMap]\n );\n\n const refetch = useCallback(() => {\n dashboardResult.refetch();\n quotasSummaryResult.refetch();\n subscriptionsResult.refetch();\n }, [dashboardResult, quotasSummaryResult, subscriptionsResult]);\n\n return {\n dashboard: dashboardResult.dashboard,\n pooledQuotas,\n activeSubscriptions,\n pastSubscriptions,\n isLoading:\n dashboardResult.isLoading || quotasSummaryResult.isLoading || subscriptionsResult.isLoading,\n isPastSubscriptionsLoading: subscriptionsResult.isPastSubscriptionsLoading,\n error: dashboardResult.error || quotasSummaryResult.error || subscriptionsResult.error,\n refetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions: subscriptionsResult.loadMorePastSubscriptions,\n hasMorePastSubscriptions: subscriptionsResult.hasMorePastSubscriptions,\n isLoadingMorePast: subscriptionsResult.isLoadingMorePast,\n pastSubscriptionsCount: subscriptionsResult.pastSubscriptionsCount,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useUsage.js","names":[],"sources":["../../../../../src/billing/modules/usage/hooks/useUsage.ts"],"sourcesContent":["/**\n * Usage Module - Hooks\n * Custom hooks for usage data fetching and mutations\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react';\nimport {\n useGetUsageDashboardQuery,\n useGetBillingAccountQuotasSummaryForUsageQuery,\n useGetSubscriptionsWithQuotasQuery,\n useGetPastSubscriptionsWithQuotasQuery,\n useGetBillingAccountsForUsageQuery,\n useGetQuotaAnalyticsForUsageQuery,\n type GetPastSubscriptionsWithQuotasQuery,\n} from '../../../../generated/global-operations';\nimport {\n useGetUsageRecordsQuery,\n type GetUsageRecordsQuery,\n type GetUsageRecordsQueryVariables,\n} from '../../../../generated/wspace-operations';\nimport { useGatewayClient, useQuery, useMutation, gql } from '@burdenoff/fe-libs/shared/graphql';\nimport { AnalyticsPeriod, QuotaStatus } from '../../../shared/types';\nimport type { UsageDashboard } from '../../../shared/types';\n\n// ============================================================================\n// Workspace Quota Assignment — managed by activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_OVERVIEW_QUERY = gql`\n query WorkspaceQuotaOverview($workspaceId: ID!, $productId: ID) {\n workspaceQuotaOverview(workspaceId: $workspaceId, productId: $productId) {\n assignment {\n id\n workspaceId\n quotaName\n productId\n quotaMode\n billingAccountId\n subscriptionId\n limit\n used\n noLimit\n resetPeriod\n lastResetAt\n isActive\n createdAt\n }\n liveStatus {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n effectiveUsed\n remaining\n usagePercentage\n }\n }\n`;\n\nconst UNASSIGN_WORKSPACE_QUOTA_MUTATION = gql`\n mutation UnassignWorkspaceQuota($id: ID!, $workspaceId: ID!) {\n unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)\n }\n`;\n\nexport interface WorkspaceQuotaAssignmentItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode: 'dedicated' | 'pooled';\n billingAccountId: string | null;\n subscriptionId: string | null;\n limit: number | null;\n used: number;\n noLimit: boolean;\n resetPeriod: string;\n lastResetAt: string | null;\n isActive: boolean;\n createdAt: string;\n}\n\nexport interface WorkspaceQuotaOverviewItem {\n assignment: WorkspaceQuotaAssignmentItem;\n liveStatus: WorkspaceQuotaStatusItem | null;\n effectiveUsed: number;\n remaining: number | null;\n usagePercentage: number;\n}\n\nexport interface AssignWorkspaceQuotaInput {\n workspaceId: string;\n quotaName: string;\n productId: string;\n quotaMode?: 'dedicated' | 'pooled';\n billingAccountId?: string;\n subscriptionId?: string;\n limit?: number;\n noLimit?: boolean;\n resetPeriod?: string;\n metadata?: Record<string, unknown>;\n}\n\n// Workspace *listing* for the current actor lives on the global/tenant layer\n// (global.graphql: `myWorkspaces(limit, offset)`), not the workspace layer —\n// there's a same-named field on the workspace-layer schema too, but that one\n// is a different resolver on a different service. microfe-workspaces' own\n// useMyWorkspaces hook queries the global gateway for exactly this reason;\n// mirror that here rather than the workspace client used by the rest of this\n// file's quota-overview hooks.\nconst MY_WORKSPACES_QUERY = gql`\n query MyWorkspacesForBilling($limit: Int, $offset: Int) {\n myWorkspaces(limit: $limit, offset: $offset) {\n items {\n id\n name\n }\n total\n }\n }\n`;\n\nexport interface MyWorkspaceOption {\n id: string;\n name: string;\n}\n\ninterface UseMyWorkspacesResult {\n workspaces: MyWorkspaceOption[];\n isLoading: boolean;\n error: Error | null;\n}\n\n/**\n * The actor's real workspace list — used to let the user pick which\n * workspace a plan/addon's quota should activate for. Orgs with a single\n * workspace never need this (auto-activated server-side); orgs with 2+ do.\n */\nexport function useMyWorkspaces(): UseMyWorkspacesResult {\n const globalClient = useGatewayClient('global');\n const { data, loading, error } = useQuery<{\n myWorkspaces: { items: MyWorkspaceOption[]; total: number };\n }>(MY_WORKSPACES_QUERY, {\n client: globalClient,\n variables: { limit: 100, offset: 0 },\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n workspaces: data?.myWorkspaces.items ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\ninterface UseWorkspaceQuotaOverviewResult {\n overview: WorkspaceQuotaOverviewItem[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useWorkspaceQuotaOverview(\n workspaceId?: string,\n productId?: string\n): UseWorkspaceQuotaOverviewResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useQuery<{\n workspaceQuotaOverview: WorkspaceQuotaOverviewItem[];\n }>(WORKSPACE_QUOTA_OVERVIEW_QUERY, {\n client: wsClient,\n variables: { workspaceId, productId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n overview: data?.workspaceQuotaOverview ?? [],\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// activateSubscriptionForWorkspace — single backend call that assigns all\n// WORKSPACE-scoped quotas from the purchased plan. Replaces the N individual\n// assignWorkspaceQuota calls so the UI never sends quota names/limits to the server.\n// ============================================================================\n\nconst ACTIVATE_SUBSCRIPTION_FOR_WORKSPACE_MUTATION = gql`\n mutation ActivateSubscriptionForWorkspace(\n $subscriptionId: ID!\n $billingAccountId: ID!\n $workspaceId: ID!\n ) {\n activateSubscriptionForWorkspace(\n subscriptionId: $subscriptionId\n billingAccountId: $billingAccountId\n workspaceId: $workspaceId\n ) {\n workspaceId\n assignedQuotas\n skippedQuotas\n }\n }\n`;\n\nexport interface ActivateSubscriptionForWorkspaceResult {\n workspaceId: string;\n assignedQuotas: number;\n skippedQuotas: number;\n}\n\ninterface UseActivateSubscriptionForWorkspaceResult {\n activate: (\n subscriptionId: string,\n billingAccountId: string,\n workspaceId: string\n ) => Promise<ActivateSubscriptionForWorkspaceResult | null>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useActivateSubscriptionForWorkspace(): UseActivateSubscriptionForWorkspaceResult {\n const globalClient = useGatewayClient('global');\n const [mutate, { loading, error }] = useMutation<\n { activateSubscriptionForWorkspace: ActivateSubscriptionForWorkspaceResult },\n { subscriptionId: string; billingAccountId: string; workspaceId: string }\n >(ACTIVATE_SUBSCRIPTION_FOR_WORKSPACE_MUTATION, { client: globalClient });\n\n const activate = async (\n subscriptionId: string,\n billingAccountId: string,\n workspaceId: string\n ) => {\n const result = await mutate({ variables: { subscriptionId, billingAccountId, workspaceId } });\n return result.data?.activateSubscriptionForWorkspace ?? null;\n };\n\n return { activate, isLoading: loading, error: error ?? null };\n}\n\ninterface UseUnassignWorkspaceQuotaResult {\n unassign: (id: string, workspaceId: string) => Promise<boolean>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useUnassignWorkspaceQuota(): UseUnassignWorkspaceQuotaResult {\n const wsClient = useGatewayClient('workspace');\n const [mutate, { loading, error }] = useMutation<\n { unassignWorkspaceQuota: boolean },\n { id: string; workspaceId: string }\n >(UNASSIGN_WORKSPACE_QUOTA_MUTATION, { client: wsClient });\n\n const unassign = async (id: string, workspaceId: string) => {\n const result = await mutate({ variables: { id, workspaceId } });\n return result.data?.unassignWorkspaceQuota ?? false;\n };\n\n return { unassign, isLoading: loading, error: error ?? null };\n}\n\n// ============================================================================\n// Workspace Quota Status — live values from activity-svc\n// ============================================================================\n\nconst WORKSPACE_QUOTA_STATUSES_QUERY = gql`\n query WorkspaceQuotaStatuses($workspaceId: ID!) {\n workspaceQuotaStatuses(workspaceId: $workspaceId) {\n workspaceId\n quotaName\n productId\n currentValue\n limit\n noLimit\n scrapedAt\n }\n }\n`;\n\nconst WORKSPACE_QUOTA_HISTORY_QUERY = gql`\n query WorkspaceQuotaHistory($workspaceId: ID!, $quotaName: String!, $limit: Int) {\n workspaceQuotaHistory(workspaceId: $workspaceId, quotaName: $quotaName, limit: $limit) {\n id\n workspaceId\n quotaName\n productId\n value\n limit\n noLimit\n snapshotAt\n }\n }\n`;\n\nexport interface WorkspaceQuotaStatusItem {\n workspaceId: string;\n quotaName: string;\n productId: string;\n currentValue: number;\n limit: number | null;\n noLimit: boolean;\n scrapedAt: string;\n}\n\nexport interface WorkspaceQuotaSnapshotItem {\n id: string;\n workspaceId: string;\n quotaName: string;\n productId: string;\n value: number;\n limit: number | null;\n noLimit: boolean;\n snapshotAt: string;\n}\n\ninterface UseWorkspaceQuotaStatusesResult {\n statuses: WorkspaceQuotaStatusItem[];\n statusMap: Map<string, WorkspaceQuotaStatusItem>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaStatuses(workspaceId?: string): UseWorkspaceQuotaStatusesResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{ workspaceQuotaStatuses: WorkspaceQuotaStatusItem[] }>(\n WORKSPACE_QUOTA_STATUSES_QUERY,\n {\n client: wsClient,\n variables: { workspaceId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n }\n );\n\n const { statuses, statusMap } = useMemo(() => {\n const list = data?.workspaceQuotaStatuses ?? [];\n return { statuses: list, statusMap: new Map(list.map((s) => [s.quotaName, s])) };\n }, [data]);\n\n return { statuses, statusMap, isLoading: loading, error: error ?? null };\n}\n\ninterface UseWorkspaceQuotaHistoryResult {\n snapshots: WorkspaceQuotaSnapshotItem[];\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useWorkspaceQuotaHistory(\n workspaceId?: string,\n quotaName?: string,\n limit: number = 48\n): UseWorkspaceQuotaHistoryResult {\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error } = useQuery<{\n workspaceQuotaHistory: WorkspaceQuotaSnapshotItem[];\n }>(WORKSPACE_QUOTA_HISTORY_QUERY, {\n client: wsClient,\n variables: { workspaceId, quotaName, limit },\n skip: !workspaceId || !quotaName,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n snapshots: data?.workspaceQuotaHistory ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n// ============================================================================\n// Dashboard Hook\n// ============================================================================\n\ninterface UseUsageDashboardParams {\n billingAccountId?: string;\n period?: AnalyticsPeriod;\n}\n\ninterface UseUsageDashboardResult {\n dashboard: UsageDashboard | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useUsageDashboard({\n billingAccountId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n}: UseUsageDashboardParams): UseUsageDashboardResult {\n const { data, loading, error, refetch } = useGetUsageDashboardQuery({\n variables: { billingAccountId: billingAccountId!, period },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n dashboard: data?.getUsageDashboard ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n// ============================================================================\n// Enhanced Usage Page Hooks\n// ============================================================================\n\n/**\n * Quota assignment from the API\n */\ninterface QuotaAssignmentSummary {\n id: string;\n name: string;\n unit: string;\n quotaType: 'POOLED' | 'PRODUCT_SPECIFIC';\n reusable: boolean;\n limit: number;\n limitLeft: number;\n used: number;\n usagePercentage: number;\n noLimit: boolean;\n subscriptionId?: string;\n context?: Record<string, unknown> | null;\n recentUsages: Array<{\n id: string;\n amount: number;\n description: string;\n timestamp: string;\n tags?: Record<string, unknown>;\n }>;\n createdAt: string;\n endtime: string;\n}\n\n/**\n * Aggregated quota for display (multiple quota assignments with same name combined)\n */\nexport interface AggregatedQuota {\n name: string;\n displayName?: string;\n description?: string;\n unit: string;\n totalLimit: number;\n totalUsed: number;\n usagePercentage: number;\n status: QuotaStatus;\n quotaCount: number;\n noLimit: boolean;\n reusable: boolean;\n /** True when usage comes from the activity-svc scraper, not billing recordUsage events. */\n isCollectorBased: boolean;\n quotaAssignments: QuotaAssignmentSummary[];\n}\n\n/**\n * Subscription with aggregated quotas\n */\nexport interface SubscriptionWithAggregatedQuotas {\n id: string;\n status: string;\n planName?: string;\n productId?: string;\n createdAt: string;\n aggregatedQuotas: AggregatedQuota[];\n}\n\n/**\n * Helper function to calculate quota status based on usage percentage\n */\nfunction calculateQuotaStatus(\n usagePercentage: number,\n limitLeft: number,\n noLimit = false\n): QuotaStatus {\n if (noLimit) return QuotaStatus.HEALTHY;\n if (limitLeft <= 0) return QuotaStatus.EXHAUSTED;\n if (usagePercentage >= 90) return QuotaStatus.CRITICAL;\n if (usagePercentage >= 75) return QuotaStatus.WARNING;\n return QuotaStatus.HEALTHY;\n}\n\n// Maps billing quota names to their corresponding scraper metric names in workspace_quota_statuses.\n// Scraper keys use dot-notation product prefix; billing keys use kebab-case.\nconst BILLING_TO_SCRAPER_METRIC: Record<string, string> = {\n 'agents-registered': 'vibecontrols.agents.count',\n 'vibes-created': 'vibecontrols.vibes.count',\n 'catalogs-created': 'vibecontrols.catalogs.count',\n 'tunnels-created': 'vibecontrols.tunnels.count',\n 'sessions-started': 'vibecontrols.sessions.starts',\n};\n\n/**\n * Merges live collector values from activity-svc into a list of aggregated quotas.\n * Overrides `totalUsed` (and derived fields) when a live value exists, and distributes\n * the live total across individual assignments (earliest-expiry first) so the\n * Quota Breakdown shows real per-assignment consumption instead of always-0.\n */\nfunction mergeLiveValues(\n aggregated: AggregatedQuota[],\n statusMap: Map<string, WorkspaceQuotaStatusItem>\n): AggregatedQuota[] {\n if (statusMap.size === 0) return aggregated;\n return aggregated.map((q) => {\n const scraperKey = BILLING_TO_SCRAPER_METRIC[q.name] ?? q.name;\n const live = statusMap.get(scraperKey);\n if (!live) return q;\n\n const totalUsed = live.currentValue;\n const { totalLimit, noLimit } = q;\n const usagePercentage = noLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n const limitLeft = noLimit ? 0 : Math.max(0, totalLimit - totalUsed);\n\n // Distribute live total into assignments: fill earliest-expiry first, overflow to next.\n let remaining = totalUsed;\n const updatedAssignments = [...q.quotaAssignments]\n .sort((a, b) => {\n const dateA = a.endtime ? new Date(a.endtime).getTime() : Infinity;\n const dateB = b.endtime ? new Date(b.endtime).getTime() : Infinity;\n return dateA - dateB;\n })\n .map((a) => {\n const assignmentUsed = Math.min(remaining, a.limit || 0);\n remaining = Math.max(0, remaining - assignmentUsed);\n return { ...a, used: assignmentUsed };\n });\n\n return {\n ...q,\n totalUsed,\n usagePercentage,\n isCollectorBased: true,\n status: calculateQuotaStatus(usagePercentage, limitLeft, noLimit),\n quotaAssignments: updatedAssignments,\n };\n });\n}\n\n/**\n * Helper function to aggregate quotas by name\n */\nfunction aggregateQuotasByName(quotas: QuotaAssignmentSummary[]): AggregatedQuota[] {\n const grouped = new Map<string, QuotaAssignmentSummary[]>();\n\n for (const quota of quotas) {\n const existing = grouped.get(quota.name) || [];\n existing.push(quota);\n grouped.set(quota.name, existing);\n }\n\n const aggregated: AggregatedQuota[] = [];\n\n for (const [name, assignments] of grouped) {\n // Sum up limits and usage first\n const totalLimit = assignments.reduce((sum, q) => sum + (q.limit || 0), 0);\n const totalUsed = assignments.reduce((sum, q) => sum + (q.used || 0), 0);\n const totalLimitLeft = assignments.reduce((sum, q) => sum + (q.limitLeft || 0), 0);\n\n // Check if there's actually no limit (ignore noLimit flag if there's a numeric limit)\n // Only treat as unlimited if totalLimit is 0 or all assignments explicitly have noLimit AND no numeric limit\n const hasNoLimit =\n totalLimit === 0 && assignments.every((q) => q.noLimit || (q.limit || 0) === 0);\n\n // Calculate percentage\n const usagePercentage = hasNoLimit || totalLimit === 0 ? 0 : (totalUsed / totalLimit) * 100;\n\n // Get status\n const status = calculateQuotaStatus(usagePercentage, totalLimitLeft, hasNoLimit);\n\n // Get reusable, unit, and display info from first assignment\n const reusable = assignments[0]?.reusable || false;\n const unit = assignments[0]?.unit || '';\n const ctx = assignments[0]?.context;\n const displayName = typeof ctx?.displayName === 'string' ? ctx.displayName : undefined;\n const description = typeof ctx?.description === 'string' ? ctx.description : undefined;\n\n aggregated.push({\n name,\n displayName,\n description,\n unit,\n totalLimit: hasNoLimit ? 0 : totalLimit,\n totalUsed,\n usagePercentage,\n status,\n quotaCount: assignments.length,\n noLimit: hasNoLimit,\n isCollectorBased: false,\n reusable,\n quotaAssignments: assignments,\n });\n }\n\n // Sort by status priority (critical first) then by usage percentage\n const statusPriority: Record<QuotaStatus, number> = {\n EXHAUSTED: 0,\n CRITICAL: 1,\n WARNING: 2,\n HEALTHY: 3,\n INACTIVE: 4,\n };\n\n return aggregated.sort((a, b) => {\n const priorityDiff = statusPriority[a.status] - statusPriority[b.status];\n if (priorityDiff !== 0) return priorityDiff;\n return b.usagePercentage - a.usagePercentage;\n });\n}\n\n/**\n * Hook to get billing accounts for the selector\n */\ninterface UseBillingAccountsForUsageResult {\n billingAccounts: Array<{\n id: string;\n name: string;\n email: string;\n currency: string;\n creditAmount: number;\n isActive: boolean;\n isDefault: boolean;\n }>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useBillingAccountsForUsage(): UseBillingAccountsForUsageResult {\n const { data, loading, error } = useGetBillingAccountsForUsageQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n billingAccounts: data?.getBillingAccountsByOrg ?? [],\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Hook to get billing account quotas summary with aggregation\n * Only returns pooled quotas - subscription quotas come from useSubscriptionsWithQuotas\n */\ninterface UseBillingAccountQuotasSummaryResult {\n pooledQuotas: AggregatedQuota[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\nexport function useBillingAccountQuotasSummary(\n billingAccountId: string | undefined,\n productId?: string\n): UseBillingAccountQuotasSummaryResult {\n const { data, loading, error, refetch } = useGetBillingAccountQuotasSummaryForUsageQuery({\n variables: { billingAccountId: billingAccountId!, productId },\n skip: !billingAccountId,\n fetchPolicy: 'cache-and-network',\n });\n\n const pooledQuotas = useMemo(() => {\n if (!data?.getBillingAccountQuotasSummary) {\n return [];\n }\n\n const summary = data.getBillingAccountQuotasSummary;\n return aggregateQuotasByName(summary.pooledQuotas || []);\n }, [data]);\n\n return {\n pooledQuotas,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Subscription from the API\n */\ninterface SubscriptionQuota {\n id: string;\n name: string;\n limits: { type: string; value: number; unit?: string; originalValue?: number };\n context?: Record<string, unknown> | null;\n currentUsageSum: number;\n usageCount: number;\n createdAt: string;\n endtime: string;\n}\n\ninterface SubscriptionFromAPI {\n id: string;\n status: string;\n createdAt: string;\n planName?: string | null;\n productId?: string | null;\n quotas: SubscriptionQuota[];\n}\n\n/**\n * Hook to get subscriptions with their quotas grouped by status\n * Uses lazy loading for past subscriptions and supports backend search\n */\ntype ViewMode = 'active' | 'past' | 'all';\n\nconst PAST_SUBSCRIPTIONS_PAGE_SIZE = 10;\n\ninterface UseSubscriptionsWithQuotasOptions {\n searchQuery?: string;\n viewMode?: ViewMode;\n productId?: string;\n}\n\ninterface UseSubscriptionsWithQuotasResult {\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n refetch: () => void;\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useSubscriptionsWithQuotas(\n billingAccountId: string | undefined,\n options: UseSubscriptionsWithQuotasOptions = {}\n): UseSubscriptionsWithQuotasResult {\n const { searchQuery, viewMode = 'active', productId } = options;\n\n // Determine which subscriptions to fetch based on viewMode\n const shouldFetchActive = viewMode === 'active' || viewMode === 'all';\n const shouldFetchPast = viewMode === 'past' || viewMode === 'all';\n\n // Query for active subscriptions\n const { data, loading, error, refetch } = useGetSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n productId: productId || null,\n },\n skip: !billingAccountId || !shouldFetchActive,\n fetchPolicy: 'cache-and-network',\n });\n\n // Query for past subscriptions with pagination - only when viewMode is 'past' or 'all'\n const {\n data: pastData,\n loading: pastLoading,\n refetch: refetchPast,\n fetchMore: fetchMorePast,\n } = useGetPastSubscriptionsWithQuotasQuery({\n variables: {\n billingAccountId: billingAccountId!,\n searchQuery: searchQuery || null,\n page: 1,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n productId: productId || null,\n },\n skip: !billingAccountId || !shouldFetchPast,\n fetchPolicy: 'cache-and-network',\n });\n\n // Track if we're loading more past subscriptions\n const [isLoadingMorePast, setIsLoadingMorePast] = useState(false);\n const [currentPastPage, setCurrentPastPage] = useState(1);\n\n // Helper function to process subscription data\n const processSubscription = useCallback(\n (sub: SubscriptionFromAPI): SubscriptionWithAggregatedQuotas => {\n const quotaSummaries: QuotaAssignmentSummary[] = (sub.quotas || []).map((q) => {\n const limit = q.limits?.value || 0;\n const used = q.currentUsageSum || 0;\n const limitLeft = Math.max(0, limit - used);\n const usagePercentage = limit > 0 ? (used / limit) * 100 : 0;\n const unit = q.limits?.unit || '';\n\n return {\n id: q.id,\n name: q.name,\n unit,\n quotaType: 'PRODUCT_SPECIFIC' as const,\n reusable: false,\n limit,\n limitLeft,\n used,\n usagePercentage,\n noLimit: q.limits?.type !== 'number' && q.limits?.type !== 'numeric',\n subscriptionId: sub.id,\n context: q.context,\n recentUsages: [],\n createdAt: q.createdAt,\n endtime: q.endtime,\n };\n });\n\n return {\n id: sub.id,\n status: sub.status,\n createdAt: sub.createdAt,\n planName: sub.planName ?? undefined,\n productId: sub.productId ?? undefined,\n aggregatedQuotas: aggregateQuotasByName(quotaSummaries),\n };\n },\n []\n );\n\n // Process active subscriptions\n const activeSubscriptions = useMemo(() => {\n if (!data?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] = data.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [data, processSubscription]);\n\n // Process past subscriptions\n const pastSubscriptions = useMemo(() => {\n if (!pastData?.getSubscriptionUsageOverview) {\n return [];\n }\n\n const subscriptions: SubscriptionFromAPI[] =\n pastData.getSubscriptionUsageOverview.subscriptions || pastData.getSubscriptionUsageOverview;\n return subscriptions.map(processSubscription);\n }, [pastData, processSubscription]);\n\n // Get pagination info for past subscriptions\n const pastSubscriptionsPagination = useMemo(() => {\n const overview = pastData?.getSubscriptionUsageOverview;\n if (!overview) {\n return { totalCount: 0, hasMore: false };\n }\n // If it's the new paginated format\n if (overview.totalCount !== undefined) {\n return {\n totalCount: overview.totalCount,\n hasMore: pastSubscriptions.length < overview.totalCount,\n };\n }\n // Legacy format - assume there might be more if we got a full page\n return {\n totalCount: pastSubscriptions.length,\n hasMore: pastSubscriptions.length >= PAST_SUBSCRIPTIONS_PAGE_SIZE * currentPastPage,\n };\n }, [pastData, pastSubscriptions.length, currentPastPage]);\n\n // Reset page when search query or viewMode changes\n useEffect(() => {\n setCurrentPastPage(1);\n }, [searchQuery, viewMode]);\n\n // Load more past subscriptions\n const loadMorePastSubscriptions = useCallback(async () => {\n if (!fetchMorePast || isLoadingMorePast || !pastSubscriptionsPagination.hasMore) return;\n\n setIsLoadingMorePast(true);\n const nextPage = currentPastPage + 1;\n\n try {\n await fetchMorePast({\n variables: {\n page: nextPage,\n pageSize: PAST_SUBSCRIPTIONS_PAGE_SIZE,\n },\n updateQuery: (\n prev: GetPastSubscriptionsWithQuotasQuery,\n { fetchMoreResult }: { fetchMoreResult: GetPastSubscriptionsWithQuotasQuery }\n ) => {\n if (!fetchMoreResult) return prev;\n\n const prevSubs = prev?.getSubscriptionUsageOverview ?? [];\n const newSubs = fetchMoreResult?.getSubscriptionUsageOverview ?? [];\n\n return {\n getSubscriptionUsageOverview: [...prevSubs, ...newSubs],\n };\n },\n });\n setCurrentPastPage(nextPage);\n } catch (err) {\n console.error('Failed to load more past subscriptions:', err);\n } finally {\n setIsLoadingMorePast(false);\n }\n }, [fetchMorePast, isLoadingMorePast, pastSubscriptionsPagination.hasMore, currentPastPage]);\n\n const combinedRefetch = useCallback(() => {\n setCurrentPastPage(1);\n if (shouldFetchActive) refetch();\n if (shouldFetchPast) refetchPast();\n }, [shouldFetchActive, shouldFetchPast, refetch, refetchPast]);\n\n return {\n activeSubscriptions,\n pastSubscriptions,\n isLoading: shouldFetchActive ? loading : false,\n isPastSubscriptionsLoading: shouldFetchPast ? pastLoading : false,\n error: error ?? null,\n refetch: combinedRefetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions,\n hasMorePastSubscriptions: pastSubscriptionsPagination.hasMore,\n isLoadingMorePast,\n pastSubscriptionsCount: pastSubscriptionsPagination.totalCount,\n };\n}\n\n/**\n * Hook to get quota usage details for drill-down using cursor-based pagination.\n *\n * The activity-svc API is cursor-based (limit / cursor → items / hasNextPage / nextCursor).\n * Callers should maintain a cursor stack:\n * - push nextCursor to go forward\n * - pop to go back\n * - pass null as cursor for the first page\n */\nexport interface QuotaUsageDetail {\n id: string;\n amount: number;\n description: string;\n tags?: Record<string, unknown>;\n productId?: string;\n userId?: string;\n timestamp: string;\n lastUpdated?: string;\n}\n\ninterface UseQuotaUsageDetailsResult {\n usages: QuotaUsageDetail[];\n hasNextPage: boolean;\n nextCursor: string | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\ntype UsageRecord = GetUsageRecordsQuery['getUsageRecords']['items'][number];\n\nexport function buildUsageRecordsVariables(\n quotaAssignmentId: string | undefined,\n pageSize: number,\n cursor: string | null,\n workspaceId?: string\n): GetUsageRecordsQueryVariables {\n return {\n filters: {\n quotaAssignmentId,\n workspaceId,\n },\n pagination: {\n limit: pageSize,\n ...(cursor ? { cursor } : {}),\n },\n };\n}\n\nexport function mapUsageRecordToQuotaUsageDetail(record: UsageRecord): QuotaUsageDetail {\n return {\n id: record.id,\n amount: record.amount,\n description: record.description ?? '',\n tags: (record.metadata as Record<string, unknown>) ?? {},\n productId: record.productId,\n userId: record.userId ?? undefined,\n timestamp: record.createdAt,\n lastUpdated: record.createdAt,\n };\n}\n\nexport function useQuotaUsageDetails(\n quotaAssignmentId: string | undefined,\n pageSize: number = 20,\n cursor: string | null = null,\n workspaceId?: string\n): UseQuotaUsageDetailsResult {\n // Query workspace gateway directly — usage records live in wspace-activity-svc.\n // Uses the dedicated workspace Apollo client so it doesn't conflict with the\n // shell's global Apollo client used by all other billing queries.\n const wsClient = useGatewayClient('workspace');\n const { data, loading, error, refetch } = useGetUsageRecordsQuery({\n client: wsClient,\n variables: buildUsageRecordsVariables(quotaAssignmentId, pageSize, cursor, workspaceId),\n skip: !quotaAssignmentId || !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const items: GetUsageRecordsQuery['getUsageRecords']['items'] =\n data?.getUsageRecords?.items ?? [];\n\n return {\n usages: items.map(mapUsageRecordToQuotaUsageDetail),\n hasNextPage: data?.getUsageRecords?.hasNextPage ?? false,\n nextCursor: data?.getUsageRecords?.nextCursor ?? null,\n isLoading: loading,\n error: error ?? null,\n refetch,\n };\n}\n\n/**\n * Hook to get quota analytics\n */\ninterface UseQuotaAnalyticsResult {\n analytics: {\n dailyUsage: Array<{ date: string; usage: number; count: number }>;\n usageByType: Array<{ resourceType: string; usage: number; count: number; percentage: number }>;\n trends?: {\n growthRate?: number;\n predictedUsage?: number;\n averageDailyUsage?: number;\n };\n peakUsage?: {\n maxDailyUsage: number;\n peakDate: string;\n peakHour?: number;\n };\n } | null;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useQuotaAnalytics(\n quotaAssignmentId: string | undefined,\n period: AnalyticsPeriod = AnalyticsPeriod.LAST_30_DAYS\n): UseQuotaAnalyticsResult {\n const { data, loading, error } = useGetQuotaAnalyticsForUsageQuery({\n variables: { quotaAssignmentId: quotaAssignmentId!, period },\n skip: !quotaAssignmentId,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n analytics: data?.getQuotaAnalytics ?? null,\n isLoading: loading,\n error: error ?? null,\n };\n}\n\n/**\n * Combined hook for the enhanced usage page\n */\ninterface UseEnhancedUsagePageParams {\n billingAccountId: string | undefined;\n workspaceId?: string;\n productId?: string;\n period?: AnalyticsPeriod;\n searchQuery?: string;\n viewMode?: ViewMode;\n}\n\ninterface UseEnhancedUsagePageResult {\n // Dashboard overview\n dashboard: UsageDashboard | null;\n\n // Pooled quotas (subscription quotas come from subscriptions directly)\n pooledQuotas: AggregatedQuota[];\n\n // Subscriptions with their quotas\n activeSubscriptions: SubscriptionWithAggregatedQuotas[];\n pastSubscriptions: SubscriptionWithAggregatedQuotas[];\n\n // Loading states\n isLoading: boolean;\n isPastSubscriptionsLoading: boolean;\n error: Error | null;\n\n // Actions\n refetch: () => void;\n\n // Pagination for past subscriptions\n loadMorePastSubscriptions: () => void;\n hasMorePastSubscriptions: boolean;\n isLoadingMorePast: boolean;\n pastSubscriptionsCount: number;\n}\n\nexport function useEnhancedUsagePage({\n billingAccountId,\n workspaceId,\n productId,\n period = AnalyticsPeriod.LAST_30_DAYS,\n searchQuery,\n viewMode = 'active',\n}: UseEnhancedUsagePageParams): UseEnhancedUsagePageResult {\n // Dashboard for overview stats\n const dashboardResult = useUsageDashboard({\n billingAccountId,\n period,\n });\n\n // Quotas summary with aggregation\n const quotasSummaryResult = useBillingAccountQuotasSummary(billingAccountId);\n\n // Live collector values from activity-svc (source of truth for count/concurrent quotas)\n const { statusMap } = useWorkspaceQuotaStatuses(workspaceId);\n\n // Subscriptions with quotas - passes searchQuery and viewMode to backend\n const subscriptionsResult = useSubscriptionsWithQuotas(billingAccountId, {\n searchQuery,\n viewMode,\n productId,\n });\n\n // Merge live values into both pooled quotas and subscription quotas\n const pooledQuotas = useMemo(\n () => mergeLiveValues(quotasSummaryResult.pooledQuotas, statusMap),\n [quotasSummaryResult.pooledQuotas, statusMap]\n );\n\n const activeSubscriptions = useMemo(\n () =>\n subscriptionsResult.activeSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.activeSubscriptions, statusMap]\n );\n\n const pastSubscriptions = useMemo(\n () =>\n subscriptionsResult.pastSubscriptions.map((sub) => ({\n ...sub,\n aggregatedQuotas: mergeLiveValues(sub.aggregatedQuotas, statusMap),\n })),\n [subscriptionsResult.pastSubscriptions, statusMap]\n );\n\n const refetch = useCallback(() => {\n dashboardResult.refetch();\n quotasSummaryResult.refetch();\n subscriptionsResult.refetch();\n }, [dashboardResult, quotasSummaryResult, subscriptionsResult]);\n\n return {\n dashboard: dashboardResult.dashboard,\n pooledQuotas,\n activeSubscriptions,\n pastSubscriptions,\n isLoading:\n dashboardResult.isLoading || quotasSummaryResult.isLoading || subscriptionsResult.isLoading,\n isPastSubscriptionsLoading: subscriptionsResult.isPastSubscriptionsLoading,\n error: dashboardResult.error || quotasSummaryResult.error || subscriptionsResult.error,\n refetch,\n // Pagination for past subscriptions\n loadMorePastSubscriptions: subscriptionsResult.loadMorePastSubscriptions,\n hasMorePastSubscriptions: subscriptionsResult.hasMorePastSubscriptions,\n isLoadingMorePast: subscriptionsResult.isLoadingMorePast,\n pastSubscriptionsCount: subscriptionsResult.pastSubscriptionsCount,\n };\n}\n"],"mappings":"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"}
|