@streamoid/settings 0.13.0 → 0.15.0

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/index.js CHANGED
@@ -34,7 +34,9 @@ __export(index_exports, {
34
34
  CreditPoolMemberModal: () => CreditPoolMemberModal,
35
35
  DEFAULT_PERMISSIONS: () => DEFAULT_PERMISSIONS,
36
36
  DEFAULT_USER_LANGUAGE: () => DEFAULT_USER_LANGUAGE,
37
+ EMPTY_USAGE_LOGS_PAGE: () => EMPTY_USAGE_LOGS_PAGE,
37
38
  ExportDateModal: () => ExportDateModal,
39
+ INITIAL_USAGE_LOGS_STATE: () => INITIAL_USAGE_LOGS_STATE,
38
40
  InviteUpdateModal: () => InviteUpdateModal,
39
41
  MemberCreditLimitModal: () => MemberCreditLimitModal,
40
42
  MemberListFrame: () => MemberListFrame,
@@ -67,7 +69,12 @@ __export(index_exports, {
67
69
  TeamsContent: () => TeamsContent,
68
70
  TokensContent: () => TokensContent,
69
71
  URL_TO_TAB: () => URL_TO_TAB,
72
+ USAGE_LOGS_EXPORT_BATCH_SIZE: () => USAGE_LOGS_EXPORT_BATCH_SIZE,
73
+ USAGE_LOGS_EXPORT_EXTRA_REQUESTS: () => USAGE_LOGS_EXPORT_EXTRA_REQUESTS,
74
+ USAGE_LOGS_EXPORT_MAX_ROWS: () => USAGE_LOGS_EXPORT_MAX_ROWS,
75
+ USAGE_LOGS_PAGE_SIZE: () => USAGE_LOGS_PAGE_SIZE,
70
76
  USER_LANGUAGES: () => USER_LANGUAGES,
77
+ UsageLogsPager: () => UsageLogsPager,
71
78
  WORKSPACE_LIST_PAGE_SIZE: () => WORKSPACE_LIST_PAGE_SIZE,
72
79
  WorkspaceGeneralTab: () => WorkspaceGeneralTab,
73
80
  WorkspaceListTab: () => WorkspaceListTab,
@@ -79,13 +86,17 @@ __export(index_exports, {
79
86
  cleanCreditLimitInput: () => cleanCreditLimitInput,
80
87
  cleanPoolValueInput: () => cleanPoolValueInput,
81
88
  countLabel: () => countLabel,
89
+ createUsedByNamesLoader: () => createUsedByNamesLoader,
82
90
  creditCycleUnit: () => creditCycleUnit,
83
91
  creditsMeter: () => creditsMeter,
84
92
  deriveWorkspaceInvitePermission: () => deriveWorkspaceInvitePermission,
85
93
  describeAllowance: () => describeAllowance,
86
94
  describeDefaultLimit: () => describeDefaultLimit,
87
95
  describeMemberLimit: () => describeMemberLimit,
96
+ entitlementFromBody: () => entitlementFromBody,
97
+ entitlementFromFailure: () => entitlementFromFailure,
88
98
  expiryChoiceFor: () => expiryChoiceFor,
99
+ fetchUsageLogsInRange: () => fetchUsageLogsInRange,
89
100
  findBillingError: () => findBillingError,
90
101
  findPoolError: () => findPoolError,
91
102
  formatCreditLimit: () => formatCreditLimit,
@@ -93,16 +104,21 @@ __export(index_exports, {
93
104
  formatPoolEndsAt: () => formatPoolEndsAt,
94
105
  formatPoolSplit: () => formatPoolSplit,
95
106
  groupWorkspaceList: () => groupWorkspaceList,
107
+ hasPaidFeatures: () => hasPaidFeatures,
96
108
  isLimitBelowUsage: () => isLimitBelowUsage,
109
+ isPaidPlan: () => isPaidPlan,
110
+ isSponsoredEntitlement: () => isSponsoredEntitlement,
97
111
  joinMembersWithLimits: () => joinMembersWithLimits,
98
112
  lastUsedLabel: () => lastUsedLabel,
99
113
  limitBelowUsageUsed: () => limitBelowUsageUsed,
100
114
  limitSourceLabel: () => limitSourceLabel,
115
+ loadCurrentPlan: () => loadCurrentPlan,
101
116
  loadWorkspaceOrganization: () => loadWorkspaceOrganization,
102
117
  memberRoleUpdate: () => memberRoleUpdate,
103
118
  minPoolEndDate: () => minPoolEndDate,
104
119
  minimumCreditLimit: () => minimumCreditLimit,
105
120
  ownCreditsFirstText: () => ownCreditsFirstText,
121
+ parseBillingEntitlement: () => parseBillingEntitlement,
106
122
  pausedOwnCreditsText: () => pausedOwnCreditsText,
107
123
  poolEndsAtFrom: () => poolEndsAtFrom,
108
124
  poolErrorMessage: () => poolErrorMessage,
@@ -115,7 +131,17 @@ __export(index_exports, {
115
131
  settingsReturnTarget: () => settingsReturnTarget,
116
132
  sharedPoolBannerText: () => sharedPoolBannerText,
117
133
  sharedPoolEndsText: () => sharedPoolEndsText,
134
+ sliceUsageLogsPage: () => sliceUsageLogsPage,
135
+ sponsoredCreditsText: () => sponsoredCreditsText,
136
+ toCsv: () => toCsv,
118
137
  toLocalDateInput: () => toLocalDateInput,
138
+ usageLogsCsvRows: () => usageLogsCsvRows,
139
+ usageLogsDateRange: () => usageLogsDateRange,
140
+ usageLogsExportFileName: () => usageLogsExportFileName,
141
+ usageLogsPage: () => usageLogsPage,
142
+ usageLogsReducer: () => usageLogsReducer,
143
+ usageLogsSpentByOthers: () => usageLogsSpentByOthers,
144
+ useBillingEntitlement: () => useBillingEntitlement,
119
145
  useSettingsAdapters: () => useSettingsAdapters,
120
146
  useWorkspaceInvitePermission: () => useWorkspaceInvitePermission,
121
147
  useWorkspaceOrganization: () => useWorkspaceOrganization,
@@ -7888,7 +7914,10 @@ function formatDateRange(date) {
7888
7914
  function ExportDateModal({
7889
7915
  title,
7890
7916
  onClose,
7891
- onExport
7917
+ onExport,
7918
+ busy = false,
7919
+ busyText,
7920
+ notice = null
7892
7921
  }) {
7893
7922
  useOverlayOpen(true, onClose);
7894
7923
  const now = /* @__PURE__ */ new Date();
@@ -7975,6 +8004,7 @@ function ExportDateModal({
7975
8004
  [rangeStart, rangeEnd, isFuture]
7976
8005
  );
7977
8006
  const handleExport = () => {
8007
+ if (busy) return;
7978
8008
  if (rangeStart && rangeEnd) onExport(rangeStart, rangeEnd);
7979
8009
  };
7980
8010
  const getCellVisuals = (cell, idx) => {
@@ -8196,24 +8226,42 @@ function ExportDateModal({
8196
8226
  borderTop: "1px solid var(--alias-border-subtle)"
8197
8227
  },
8198
8228
  children: [
8199
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8200
- "p",
8201
- {
8202
- className: "flex-1 min-w-0 truncate",
8203
- style: {
8204
- fontFamily: "var(--font-family-font-family-body)",
8205
- fontSize: "var(--font-size-font-size-md)",
8206
- fontWeight: 400,
8207
- lineHeight: "var(--line-height-line-height-md)",
8208
- color: "var(--alias-text---icons-primary)"
8209
- },
8210
- children: rangeStart && rangeEnd ? `${formatDateRange(rangeStart)} - ${formatDateRange(rangeEnd)}` : rangeStart ? `${formatDateRange(rangeStart)} - Select end date` : "Select date range"
8211
- }
8212
- ),
8229
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex-1 min-w-0 flex flex-col", children: [
8230
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8231
+ "p",
8232
+ {
8233
+ className: "min-w-0 truncate",
8234
+ style: {
8235
+ fontFamily: "var(--font-family-font-family-body)",
8236
+ fontSize: "var(--font-size-font-size-md)",
8237
+ fontWeight: 400,
8238
+ lineHeight: "var(--line-height-line-height-md)",
8239
+ color: "var(--alias-text---icons-primary)"
8240
+ },
8241
+ "aria-live": "polite",
8242
+ children: busy && busyText ? busyText : rangeStart && rangeEnd ? `${formatDateRange(rangeStart)} - ${formatDateRange(rangeEnd)}` : rangeStart ? `${formatDateRange(rangeStart)} - Select end date` : "Select date range"
8243
+ }
8244
+ ),
8245
+ notice && !busy && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8246
+ "p",
8247
+ {
8248
+ role: "alert",
8249
+ style: {
8250
+ fontFamily: "var(--font-family-font-family-body)",
8251
+ fontSize: "var(--font-size-font-size-sm)",
8252
+ fontWeight: 400,
8253
+ lineHeight: "var(--line-height-line-height-sm)",
8254
+ color: "var(--alias-text---icons-error)"
8255
+ },
8256
+ children: notice
8257
+ }
8258
+ )
8259
+ ] }),
8213
8260
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8214
8261
  "div",
8215
8262
  {
8216
- className: `flex items-center justify-center shrink-0 ${rangeStart && rangeEnd ? "cursor-pointer" : "opacity-50 cursor-not-allowed"}`,
8263
+ className: `flex items-center justify-center shrink-0 ${rangeStart && rangeEnd && !busy ? "cursor-pointer" : "opacity-50 cursor-not-allowed"}`,
8264
+ "aria-disabled": busy || !(rangeStart && rangeEnd),
8217
8265
  style: {
8218
8266
  backgroundColor: "var(--alias-text---icons-primary)",
8219
8267
  borderRadius: "var(--radius-xl)",
@@ -8231,7 +8279,7 @@ function ExportDateModal({
8231
8279
  lineHeight: "var(--line-height-line-height-sm)",
8232
8280
  color: "var(--alias-text---icons-inverse)"
8233
8281
  },
8234
- children: "Export"
8282
+ children: busy ? "Exporting\u2026" : "Export"
8235
8283
  }
8236
8284
  )
8237
8285
  }
@@ -10585,6 +10633,261 @@ function SharedCreditsView() {
10585
10633
  ] });
10586
10634
  }
10587
10635
 
10636
+ // src/entitlement.ts
10637
+ function parseBillingEntitlement(value) {
10638
+ if (!value || typeof value !== "object") return null;
10639
+ const raw = value;
10640
+ if (typeof raw.paid !== "boolean") return null;
10641
+ const source = raw.source === "plan" || raw.source === "pool" ? raw.source : null;
10642
+ const entitlement = { paid: raw.paid, source };
10643
+ if (typeof raw.sponsorWorkspaceId === "string") entitlement.sponsorWorkspaceId = raw.sponsorWorkspaceId;
10644
+ if (typeof raw.sponsorName === "string") entitlement.sponsorName = raw.sponsorName;
10645
+ if (typeof raw.endsAt === "string" || raw.endsAt === null) entitlement.endsAt = raw.endsAt;
10646
+ return entitlement;
10647
+ }
10648
+ function entitlementFromBody(body) {
10649
+ if (!body || typeof body !== "object") return null;
10650
+ return parseBillingEntitlement(body.entitlement);
10651
+ }
10652
+ function entitlementFromFailure(failure) {
10653
+ const seen = /* @__PURE__ */ new Set();
10654
+ const visit = (value, depth) => {
10655
+ if (depth > 4 || value == null || typeof value !== "object" || seen.has(value)) return null;
10656
+ seen.add(value);
10657
+ const found = entitlementFromBody(value);
10658
+ if (found) return found;
10659
+ const error = value;
10660
+ const response = error.response;
10661
+ for (const child of [error.body, error.data, response?.data, error.cause]) {
10662
+ const nested = visit(child, depth + 1);
10663
+ if (nested) return nested;
10664
+ }
10665
+ return null;
10666
+ };
10667
+ return visit(failure, 0);
10668
+ }
10669
+ function isPaidPlan(subscription) {
10670
+ const amount = subscription?.plan?.amount;
10671
+ return amount != null && amount > 0;
10672
+ }
10673
+ function hasPaidFeatures({
10674
+ entitlement,
10675
+ subscription
10676
+ }) {
10677
+ return entitlement ? entitlement.paid : isPaidPlan(subscription);
10678
+ }
10679
+ function isSponsoredEntitlement(entitlement) {
10680
+ return entitlement?.source === "pool" && entitlement.paid;
10681
+ }
10682
+ function sponsoredCreditsText(entitlement) {
10683
+ const sponsor = entitlement.sponsorName?.trim() || "another workspace";
10684
+ const ends = entitlement.endsAt ? ` Sharing ends on ${formatPoolEndsAt(entitlement.endsAt)}.` : "";
10685
+ return `Credits shared by ${sponsor}.${ends}`;
10686
+ }
10687
+ async function loadCurrentPlan(fetchCurrentPlan) {
10688
+ let response;
10689
+ try {
10690
+ response = await fetchCurrentPlan();
10691
+ } catch (failure) {
10692
+ const entitlement2 = entitlementFromFailure(failure);
10693
+ if (!entitlement2) throw failure;
10694
+ return { subscription: null, entitlement: entitlement2, paid: entitlement2.paid };
10695
+ }
10696
+ const subscription = response?.data ?? null;
10697
+ const entitlement = entitlementFromBody(response);
10698
+ return { subscription, entitlement, paid: hasPaidFeatures({ entitlement, subscription }) };
10699
+ }
10700
+
10701
+ // src/usage-logs.ts
10702
+ var USAGE_LOGS_PAGE_SIZE = 50;
10703
+ var USAGE_LOGS_EXPORT_BATCH_SIZE = 100;
10704
+ var USAGE_LOGS_EXPORT_MAX_ROWS = 2e4;
10705
+ var USAGE_LOGS_EXPORT_EXTRA_REQUESTS = 10;
10706
+ function rowsOf(res) {
10707
+ return Array.isArray(res?.data) ? res.data : [];
10708
+ }
10709
+ function sliceUsageLogsPage(all, page, pageSize = USAGE_LOGS_PAGE_SIZE) {
10710
+ const total = all.length;
10711
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
10712
+ const safePage = Math.min(Math.max(1, Math.floor(page) || 1), totalPages);
10713
+ return {
10714
+ rows: all.slice((safePage - 1) * pageSize, safePage * pageSize),
10715
+ page: safePage,
10716
+ totalPages,
10717
+ total,
10718
+ unpaged: all
10719
+ };
10720
+ }
10721
+ function usageLogsPage(res, page, pageSize = USAGE_LOGS_PAGE_SIZE) {
10722
+ const data = rowsOf(res);
10723
+ const pagination = res?.pagination;
10724
+ if (!pagination || !Number.isFinite(pagination.total)) {
10725
+ return sliceUsageLogsPage(data, page, pageSize);
10726
+ }
10727
+ const total = Math.max(0, pagination.total);
10728
+ const limit = pagination.limit > 0 ? pagination.limit : pageSize;
10729
+ const totalPages = Math.max(
10730
+ 1,
10731
+ pagination.totalPages > 0 ? pagination.totalPages : Math.ceil(total / limit)
10732
+ );
10733
+ return {
10734
+ rows: data.slice(0, pageSize),
10735
+ page: pagination.page > 0 ? pagination.page : page,
10736
+ totalPages,
10737
+ total,
10738
+ unpaged: null
10739
+ };
10740
+ }
10741
+ var EMPTY_USAGE_LOGS_PAGE = {
10742
+ rows: [],
10743
+ page: 1,
10744
+ totalPages: 1,
10745
+ total: 0,
10746
+ unpaged: null
10747
+ };
10748
+ var INITIAL_USAGE_LOGS_STATE = {
10749
+ logs: EMPTY_USAGE_LOGS_PAGE,
10750
+ requestedPage: 1,
10751
+ loading: false,
10752
+ error: null
10753
+ };
10754
+ function usageLogsReducer(state, action) {
10755
+ switch (action.type) {
10756
+ case "request":
10757
+ return { ...state, requestedPage: action.page, loading: true, error: null };
10758
+ case "loaded":
10759
+ return {
10760
+ logs: action.logs,
10761
+ requestedPage: action.logs.page,
10762
+ loading: false,
10763
+ error: null
10764
+ };
10765
+ case "failed":
10766
+ return { ...state, loading: false, error: action.error };
10767
+ case "turn": {
10768
+ if (!state.logs.unpaged || state.error) return state;
10769
+ const logs = sliceUsageLogsPage(state.logs.unpaged, action.page);
10770
+ return { ...state, logs, requestedPage: logs.page };
10771
+ }
10772
+ case "reset":
10773
+ return INITIAL_USAGE_LOGS_STATE;
10774
+ }
10775
+ }
10776
+ function usageLogsSpentByOthers(logs, workspaceId) {
10777
+ return (logs.unpaged ?? logs.rows).some(
10778
+ (log) => usedByWorkspaceKey(log, workspaceId) !== null
10779
+ );
10780
+ }
10781
+ function createUsedByNamesLoader(save) {
10782
+ let savedFor = null;
10783
+ let inFlight = null;
10784
+ return {
10785
+ async load(workspaceId, fetchCreditPool) {
10786
+ if (savedFor === workspaceId || inFlight?.workspaceId === workspaceId) {
10787
+ return;
10788
+ }
10789
+ const lookup = { workspaceId };
10790
+ inFlight = lookup;
10791
+ try {
10792
+ const pool = await fetchCreditPool();
10793
+ if (inFlight !== lookup) return;
10794
+ if (save(workspaceId, usedByNamesFrom(pool))) savedFor = workspaceId;
10795
+ } catch {
10796
+ } finally {
10797
+ if (inFlight === lookup) inFlight = null;
10798
+ }
10799
+ },
10800
+ reset() {
10801
+ savedFor = null;
10802
+ inFlight = null;
10803
+ }
10804
+ };
10805
+ }
10806
+ function usageLogsExportFileName(start, end) {
10807
+ return `usage_logs_${toLocalDateInput(start)}_${toLocalDateInput(end)}.csv`;
10808
+ }
10809
+ function usageLogsDateRange(start, end) {
10810
+ const from = new Date(start);
10811
+ from.setHours(0, 0, 0, 0);
10812
+ const to = new Date(end);
10813
+ to.setHours(23, 59, 59, 999);
10814
+ return { startDate: from.toISOString(), endDate: to.toISOString() };
10815
+ }
10816
+ async function fetchUsageLogsInRange(fetchUsageLogs, range, {
10817
+ batchSize = USAGE_LOGS_EXPORT_BATCH_SIZE,
10818
+ maxRows = USAGE_LOGS_EXPORT_MAX_ROWS,
10819
+ maxRequests,
10820
+ onProgress,
10821
+ signal
10822
+ } = {}) {
10823
+ const first = await fetchUsageLogs({ ...range, page: 1, limit: batchSize });
10824
+ if (signal?.aborted) return { status: "cancelled" };
10825
+ if (!first?.pagination || !Number.isFinite(first.pagination.total)) {
10826
+ const from = Date.parse(range.startDate);
10827
+ const to = Date.parse(range.endDate);
10828
+ const rows2 = rowsOf(first).filter((row) => {
10829
+ const at = Date.parse(row.createdAt);
10830
+ return at >= from && at <= to;
10831
+ });
10832
+ if (rows2.length > maxRows) return { status: "too-large", total: rows2.length };
10833
+ onProgress?.(rows2.length, rows2.length);
10834
+ return { status: "ok", rows: rows2, total: rows2.length };
10835
+ }
10836
+ let total = Math.max(0, first.pagination.total);
10837
+ if (total > maxRows) return { status: "too-large", total };
10838
+ const limit = first.pagination.limit > 0 ? first.pagination.limit : batchSize;
10839
+ const requestCap = maxRequests ?? Math.ceil(maxRows / limit) + USAGE_LOGS_EXPORT_EXTRA_REQUESTS;
10840
+ const seen = /* @__PURE__ */ new Set();
10841
+ const rows = [];
10842
+ const add = (batch) => {
10843
+ for (const row of batch) {
10844
+ if (rows.length >= maxRows) return;
10845
+ if (seen.has(row._id)) continue;
10846
+ seen.add(row._id);
10847
+ rows.push(row);
10848
+ }
10849
+ };
10850
+ const hasNextAfter = (pagination, page) => typeof pagination?.hasNextPage === "boolean" ? pagination.hasNextPage : page * limit < total;
10851
+ add(rowsOf(first));
10852
+ onProgress?.(rows.length, total);
10853
+ let hasNext = hasNextAfter(first.pagination, 1);
10854
+ let requests = 1;
10855
+ for (let page = 2; hasNext && rows.length < total; page++) {
10856
+ if (requests >= requestCap) {
10857
+ return { status: "incomplete", fetched: rows.length, total };
10858
+ }
10859
+ if (signal?.aborted) return { status: "cancelled" };
10860
+ const res = await fetchUsageLogs({ ...range, page, limit });
10861
+ requests++;
10862
+ if (signal?.aborted) return { status: "cancelled" };
10863
+ if (res?.pagination && Number.isFinite(res.pagination.total)) {
10864
+ total = Math.max(0, res.pagination.total);
10865
+ if (total > maxRows) return { status: "too-large", total };
10866
+ }
10867
+ const batch = rowsOf(res);
10868
+ if (batch.length === 0) break;
10869
+ add(batch);
10870
+ onProgress?.(rows.length, total);
10871
+ hasNext = hasNextAfter(res?.pagination, page);
10872
+ }
10873
+ return { status: "ok", rows, total };
10874
+ }
10875
+ function usageLogsCsvRows(logs, userMap, formatDate5) {
10876
+ return [
10877
+ ["Date", "Type", "Credits", "Service", "User ID"],
10878
+ ...logs.map((log) => [
10879
+ formatDate5(log.createdAt),
10880
+ log.type,
10881
+ log.amount.toLocaleString("en-US"),
10882
+ log.service?.serviceGroupName ?? log.meta?.info ?? "",
10883
+ log.triggeredByUserId != null ? userMap[log.triggeredByUserId] ?? log.triggeredByUserId : ""
10884
+ ])
10885
+ ];
10886
+ }
10887
+ function toCsv(rows) {
10888
+ return rows.map((r) => r.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(",")).join("\n");
10889
+ }
10890
+
10588
10891
  // src/billing-content.tsx
10589
10892
  var import_jsx_runtime29 = require("react/jsx-runtime");
10590
10893
  var DEFAULT_TOPUP_SERVICE = "catalogix";
@@ -11470,6 +11773,8 @@ function UsageLogsTable({
11470
11773
  logs,
11471
11774
  userMap,
11472
11775
  loading,
11776
+ error = null,
11777
+ onRetry,
11473
11778
  currentWorkspaceId = null,
11474
11779
  usedByNames = {}
11475
11780
  }) {
@@ -11500,6 +11805,50 @@ function UsageLogsTable({
11500
11805
  }
11501
11806
  );
11502
11807
  }
11808
+ if (error) {
11809
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
11810
+ "div",
11811
+ {
11812
+ role: "alert",
11813
+ className: "flex flex-col items-center justify-center w-full",
11814
+ style: {
11815
+ backgroundColor: "var(--alias-surface-base)",
11816
+ borderRadius: "var(--radius-3xl)",
11817
+ border: "1px solid var(--alias-border-subtle)",
11818
+ padding: "var(--spacing-6xl)",
11819
+ gap: "var(--spacing-md)"
11820
+ },
11821
+ children: [
11822
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
11823
+ "span",
11824
+ {
11825
+ style: {
11826
+ fontFamily: "var(--font-family-font-family-body)",
11827
+ fontSize: "var(--font-size-font-size-sm)",
11828
+ fontWeight: 400,
11829
+ lineHeight: "var(--line-height-line-height-sm)",
11830
+ color: "var(--alias-text---icons-muted)",
11831
+ textAlign: "center"
11832
+ },
11833
+ children: [
11834
+ "Couldn't load usage logs. ",
11835
+ error
11836
+ ]
11837
+ }
11838
+ ),
11839
+ onRetry && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11840
+ import_ui17.ScButton,
11841
+ {
11842
+ text: "Try again",
11843
+ variant: "tertiary",
11844
+ size: "sm",
11845
+ onClick: onRetry
11846
+ }
11847
+ )
11848
+ ]
11849
+ }
11850
+ );
11851
+ }
11503
11852
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
11504
11853
  "div",
11505
11854
  {
@@ -11675,6 +12024,71 @@ function UsageLogsTable({
11675
12024
  }
11676
12025
  );
11677
12026
  }
12027
+ function UsageLogsPager({
12028
+ page,
12029
+ totalPages,
12030
+ total,
12031
+ loading = false,
12032
+ onPageChange
12033
+ }) {
12034
+ if (total <= 0) return null;
12035
+ const canPrevious = !loading && page > 1;
12036
+ const canNext = !loading && page < totalPages;
12037
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
12038
+ "div",
12039
+ {
12040
+ className: "flex items-center justify-between shrink-0 w-full",
12041
+ style: { gap: "var(--spacing-md)" },
12042
+ children: [
12043
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
12044
+ "span",
12045
+ {
12046
+ style: {
12047
+ color: "var(--alias-text---icons-muted)",
12048
+ fontFamily: "var(--font-family-font-family-body)",
12049
+ fontSize: "var(--font-size-font-size-sm)",
12050
+ lineHeight: "var(--line-height-line-height-sm)"
12051
+ },
12052
+ children: [
12053
+ "Page ",
12054
+ page.toLocaleString("en-US"),
12055
+ " of",
12056
+ " ",
12057
+ totalPages.toLocaleString("en-US"),
12058
+ " \xB7",
12059
+ " ",
12060
+ total.toLocaleString("en-US"),
12061
+ " ",
12062
+ total === 1 ? "log" : "logs"
12063
+ ]
12064
+ }
12065
+ ),
12066
+ totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center", style: { gap: "var(--spacing-md)" }, children: [
12067
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12068
+ import_ui17.ScButton,
12069
+ {
12070
+ text: "Previous",
12071
+ variant: "tertiary",
12072
+ size: "sm",
12073
+ state: canPrevious ? "default" : "disabled",
12074
+ onClick: canPrevious ? () => onPageChange(page - 1) : void 0
12075
+ }
12076
+ ),
12077
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12078
+ import_ui17.ScButton,
12079
+ {
12080
+ text: "Next",
12081
+ variant: "tertiary",
12082
+ size: "sm",
12083
+ state: canNext ? "default" : "disabled",
12084
+ onClick: canNext ? () => onPageChange(page + 1) : void 0
12085
+ }
12086
+ )
12087
+ ] })
12088
+ ]
12089
+ }
12090
+ );
12091
+ }
11678
12092
  function BackToBilling({ onClick }) {
11679
12093
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
11680
12094
  "div",
@@ -11716,7 +12130,8 @@ function BillingContent({
11716
12130
  showPerUserLimits: showPerUserLimitsProp,
11717
12131
  onShowPerUserLimitsChange,
11718
12132
  showSharedCredits: showSharedCreditsProp,
11719
- onShowSharedCreditsChange
12133
+ onShowSharedCreditsChange,
12134
+ onEntitlementChange
11720
12135
  } = {}) {
11721
12136
  const { auth, workspace, organization, api, toast, navigation } = useSettingsAdapters();
11722
12137
  const user = auth.user;
@@ -11753,15 +12168,38 @@ function BillingContent({
11753
12168
  else setShowSharedCreditsInternal(next);
11754
12169
  };
11755
12170
  const [showExportModal, setShowExportModal] = (0, import_react23.useState)(false);
12171
+ const [exportProgress, setExportProgress] = (0, import_react23.useState)(null);
12172
+ const [exportNotice, setExportNotice] = (0, import_react23.useState)(null);
12173
+ const exportAbortRef = (0, import_react23.useRef)(null);
11756
12174
  const [showBuyCreditsModal, setShowBuyCreditsModal] = (0, import_react23.useState)(false);
11757
12175
  const [subscription, setSubscription] = (0, import_react23.useState)(null);
12176
+ const [entitlement, setEntitlement] = (0, import_react23.useState)(
12177
+ null
12178
+ );
11758
12179
  const [availableCredits, setAvailableCredits] = (0, import_react23.useState)(null);
11759
12180
  const [sharedPool, setSharedPool] = (0, import_react23.useState)(null);
11760
12181
  const [usedByNames, setUsedByNames] = (0, import_react23.useState)({});
11761
12182
  const [products, setProducts] = (0, import_react23.useState)([]);
11762
12183
  const [invoices, setInvoices] = (0, import_react23.useState)([]);
11763
12184
  const [totalCredits, setTotalCredits] = (0, import_react23.useState)(null);
11764
- const [usageLogs, setUsageLogs] = (0, import_react23.useState)([]);
12185
+ const [usageLogsState, dispatchUsageLogs] = (0, import_react23.useReducer)(
12186
+ usageLogsReducer,
12187
+ INITIAL_USAGE_LOGS_STATE
12188
+ );
12189
+ const {
12190
+ logs: usageLogs,
12191
+ loading: loadingUsageLogs,
12192
+ error: usageLogsError
12193
+ } = usageLogsState;
12194
+ const usageLogsRequestRef = (0, import_react23.useRef)(0);
12195
+ const workspaceIdRef = (0, import_react23.useRef)(workspaceId);
12196
+ workspaceIdRef.current = workspaceId;
12197
+ const loadUsedByNamesRef = (0, import_react23.useRef)(null);
12198
+ loadUsedByNamesRef.current ??= createUsedByNamesLoader((forWorkspace, names) => {
12199
+ if (forWorkspace !== workspaceIdRef.current) return false;
12200
+ setUsedByNames(names);
12201
+ return true;
12202
+ });
11765
12203
  const [userMap, setUserMap] = (0, import_react23.useState)({});
11766
12204
  const [customPlanCredits, setCustomPlanCredits] = (0, import_react23.useState)(
11767
12205
  null
@@ -11800,12 +12238,15 @@ function BillingContent({
11800
12238
  try {
11801
12239
  console.log("calling Billing apis");
11802
12240
  const [subRes, creditsRes, productsRes, customRes] = await Promise.all([
11803
- api.fetchCurrentPlan().catch(() => null),
12241
+ // A sponsored workspace with no subscription of its own gets a 404
12242
+ // that carries its entitlement: loadCurrentPlan answers it, not throws.
12243
+ loadCurrentPlan(api.fetchCurrentPlan).catch(() => null),
11804
12244
  api.fetchAvailableCredits().catch(() => null),
11805
12245
  api.fetchPlans().catch(() => null),
11806
12246
  api.getCustomCreditInfo?.().catch(() => null) ?? Promise.resolve(null)
11807
12247
  ]);
11808
- if (subRes?.data) setSubscription(subRes.data);
12248
+ if (subRes?.subscription) setSubscription(subRes.subscription);
12249
+ if (subRes) setEntitlement(subRes.entitlement);
11809
12250
  if (creditsRes?.availableCredits != null)
11810
12251
  setAvailableCredits(creditsRes.availableCredits);
11811
12252
  setSharedPool(creditsRes?.sharedPool ?? null);
@@ -11842,8 +12283,68 @@ function BillingContent({
11842
12283
  } catch {
11843
12284
  }
11844
12285
  }, [api]);
12286
+ const ensureUsedByNames = (0, import_react23.useCallback)(
12287
+ (logs) => {
12288
+ const fetchCreditPool = api.fetchCreditPool;
12289
+ if (!fetchCreditPool || !workspaceId || !usageLogsSpentByOthers(logs, workspaceId)) {
12290
+ return;
12291
+ }
12292
+ void loadUsedByNamesRef.current?.load(
12293
+ workspaceId,
12294
+ () => fetchCreditPool.call(api)
12295
+ );
12296
+ },
12297
+ [api, workspaceId]
12298
+ );
12299
+ const loadUsageLogsPage = (0, import_react23.useCallback)(
12300
+ async (page) => {
12301
+ const request = ++usageLogsRequestRef.current;
12302
+ if (!api.fetchUsageLogs) {
12303
+ dispatchUsageLogs({ type: "loaded", logs: EMPTY_USAGE_LOGS_PAGE });
12304
+ return;
12305
+ }
12306
+ dispatchUsageLogs({ type: "request", page });
12307
+ try {
12308
+ const res = await api.fetchUsageLogs({
12309
+ page,
12310
+ limit: USAGE_LOGS_PAGE_SIZE
12311
+ });
12312
+ if (request !== usageLogsRequestRef.current) return;
12313
+ const next = usageLogsPage(res, page);
12314
+ dispatchUsageLogs({ type: "loaded", logs: next });
12315
+ ensureUsedByNames(next);
12316
+ } catch (err) {
12317
+ if (request !== usageLogsRequestRef.current) return;
12318
+ dispatchUsageLogs({
12319
+ type: "failed",
12320
+ error: errorMessage(err, { method: "GET" })
12321
+ });
12322
+ }
12323
+ },
12324
+ [api, ensureUsedByNames]
12325
+ );
12326
+ const goToUsageLogsPage = (0, import_react23.useCallback)(
12327
+ (page) => {
12328
+ if (usageLogs.unpaged && !usageLogsError) {
12329
+ dispatchUsageLogs({ type: "turn", page });
12330
+ ensureUsedByNames(usageLogs);
12331
+ return;
12332
+ }
12333
+ void loadUsageLogsPage(page);
12334
+ },
12335
+ [usageLogs, usageLogsError, loadUsageLogsPage, ensureUsedByNames]
12336
+ );
12337
+ (0, import_react23.useEffect)(() => {
12338
+ if (!loadingUsageLogs && !usageLogsError && usageLogs.unpaged === null && usageLogs.rows.length === 0 && usageLogs.total > 0 && usageLogs.page > usageLogs.totalPages) {
12339
+ void loadUsageLogsPage(usageLogs.totalPages);
12340
+ }
12341
+ }, [usageLogs, loadingUsageLogs, usageLogsError, loadUsageLogsPage]);
11845
12342
  const fetchTableData = (0, import_react23.useCallback)(
11846
12343
  async (tab) => {
12344
+ if (tab === "usage") {
12345
+ await loadUsageLogsPage(1);
12346
+ return;
12347
+ }
11847
12348
  setLoadingTable(true);
11848
12349
  try {
11849
12350
  if (tab === "invoice") {
@@ -11855,25 +12356,14 @@ function BillingContent({
11855
12356
  (item) => item.expiresAt && item.expiresAt.split("T")[0] > today
11856
12357
  ).reduce((total, item) => total + item.amount, 0)
11857
12358
  );
11858
- } else if (api.fetchUsageLogs) {
11859
- const res = await api.fetchUsageLogs();
11860
- const logs = res.data?.length ? res.data : [];
11861
- setUsageLogs(logs);
11862
- if (api.fetchCreditPool && logs.some((log) => usedByWorkspaceKey(log, workspaceId))) {
11863
- const pool = await api.fetchCreditPool().catch(() => null);
11864
- setUsedByNames(usedByNamesFrom(pool));
11865
- }
11866
- } else {
11867
- setUsageLogs([]);
11868
12359
  }
11869
12360
  } catch {
11870
- if (tab === "invoice") setInvoices([]);
11871
- else setUsageLogs([]);
12361
+ setInvoices([]);
11872
12362
  } finally {
11873
12363
  setLoadingTable(false);
11874
12364
  }
11875
12365
  },
11876
- [api, workspaceId]
12366
+ [api, loadUsageLogsPage]
11877
12367
  );
11878
12368
  const handlePlanAction = (0, import_react23.useCallback)(
11879
12369
  async ({ productId, priceId, planName: planName2 }) => {
@@ -11975,10 +12465,15 @@ function BillingContent({
11975
12465
  setInitialFetchTriggered(false);
11976
12466
  setBillingOverviewLoaded(false);
11977
12467
  setSubscription(null);
12468
+ setEntitlement(null);
11978
12469
  setAvailableCredits(null);
11979
12470
  setProducts([]);
11980
12471
  setTotalCredits(null);
11981
12472
  setCustomPlanCredits(null);
12473
+ usageLogsRequestRef.current++;
12474
+ dispatchUsageLogs({ type: "reset" });
12475
+ loadUsedByNamesRef.current?.reset();
12476
+ setUsedByNames({});
11982
12477
  }, [workspaceId]);
11983
12478
  (0, import_react23.useEffect)(() => {
11984
12479
  setInitialFetchTriggered(true);
@@ -12030,7 +12525,16 @@ function BillingContent({
12030
12525
  subscription?.scheduledDowngradeEffectiveDate
12031
12526
  );
12032
12527
  const scheduledDowngradeText = subscription?.downgradeScheduled && subscription.plan?.name ? `Downgrades to ${subscription.plan.name}${scheduledDowngradeDateLabel ? ` ${scheduledDowngradeDateLabel}` : ""}` : void 0;
12033
- const isPlanDetailsLoading = !billingOverviewLoaded || loadingBilling || subscription?.plan == null || !planName || planName === "-" || !planAmount || currentPlanCredits == null;
12528
+ const sponsored = isSponsoredEntitlement(entitlement);
12529
+ const sponsoredWithoutPlan = sponsored && billingOverviewLoaded && !loadingBilling && subscription == null;
12530
+ const isPlanDetailsLoading = sponsoredWithoutPlan ? false : !billingOverviewLoaded || loadingBilling || subscription?.plan == null || !planName || planName === "-" || !planAmount || currentPlanCredits == null;
12531
+ const paidFeatures = hasPaidFeatures({ entitlement, subscription });
12532
+ const onEntitlementChangeRef = (0, import_react23.useRef)(onEntitlementChange);
12533
+ onEntitlementChangeRef.current = onEntitlementChange;
12534
+ (0, import_react23.useEffect)(() => {
12535
+ if (!billingOverviewLoaded) return;
12536
+ onEntitlementChangeRef.current?.({ entitlement, paid: paidFeatures });
12537
+ }, [billingOverviewLoaded, entitlement, paidFeatures]);
12034
12538
  const {
12035
12539
  used: usedCredits,
12036
12540
  remaining: remainingCredits,
@@ -12072,35 +12576,74 @@ function BillingContent({
12072
12576
  toast.success(`Opened ${successCount} invoice PDF(s).`);
12073
12577
  }
12074
12578
  } else {
12075
- const filtered = usageLogs.filter((log) => {
12076
- const d = new Date(log.createdAt);
12077
- return d >= startDate && d <= end;
12579
+ await exportUsageLogs(startDate, endDate);
12580
+ }
12581
+ };
12582
+ const closeExportModal = () => {
12583
+ exportAbortRef.current?.abort();
12584
+ exportAbortRef.current = null;
12585
+ setExportProgress(null);
12586
+ setExportNotice(null);
12587
+ setShowExportModal(false);
12588
+ };
12589
+ (0, import_react23.useEffect)(() => () => exportAbortRef.current?.abort(), []);
12590
+ const exportUsageLogs = async (startDate, endDate) => {
12591
+ if (!api.fetchUsageLogs) {
12592
+ toast.info?.("Usage logs are not available in this workspace.");
12593
+ closeExportModal();
12594
+ return;
12595
+ }
12596
+ exportAbortRef.current?.abort();
12597
+ const controller = new AbortController();
12598
+ exportAbortRef.current = controller;
12599
+ setExportNotice(null);
12600
+ setExportProgress({ fetched: 0, total: 0 });
12601
+ const range = usageLogsDateRange(startDate, endDate);
12602
+ try {
12603
+ const result = await fetchUsageLogsInRange(api.fetchUsageLogs, range, {
12604
+ signal: controller.signal,
12605
+ onProgress: (fetched, total) => {
12606
+ if (!controller.signal.aborted) setExportProgress({ fetched, total });
12607
+ }
12078
12608
  });
12079
- if (filtered.length === 0) {
12609
+ if (result.status === "cancelled" || controller.signal.aborted) return;
12610
+ if (result.status === "too-large") {
12611
+ setExportNotice(
12612
+ `${result.total.toLocaleString("en-US")} logs in this range; an export holds up to ${USAGE_LOGS_EXPORT_MAX_ROWS.toLocaleString("en-US")}. Choose a shorter range.`
12613
+ );
12614
+ return;
12615
+ }
12616
+ if (result.status === "incomplete") {
12617
+ setExportNotice(
12618
+ "Usage logs kept changing while exporting, so some were missed. Try again."
12619
+ );
12620
+ return;
12621
+ }
12622
+ if (result.rows.length === 0) {
12080
12623
  toast.info?.("No usage logs found in the selected date range.");
12081
- setShowExportModal(false);
12624
+ closeExportModal();
12082
12625
  return;
12083
12626
  }
12084
- const headers = ["Date", "Type", "Credits", "Service", "User ID"];
12085
- const rows = filtered.map((log) => [
12086
- formatDate3(log.createdAt),
12087
- log.type,
12088
- log.amount.toLocaleString("en-US"),
12089
- log.service?.serviceGroupName ?? log.meta?.info ?? "",
12090
- log.triggeredByUserId != null ? userMap[log.triggeredByUserId] ?? log.triggeredByUserId : ""
12091
- ]);
12092
12627
  downloadCsv(
12093
- [headers, ...rows],
12094
- `usage_logs_${fmtFilename(startDate)}_${fmtFilename(end)}.csv`
12628
+ usageLogsCsvRows(result.rows, userMap, formatDate3),
12629
+ usageLogsExportFileName(startDate, endDate)
12095
12630
  );
12096
- toast.success(`Exported ${filtered.length} log(s).`);
12097
- setShowExportModal(false);
12631
+ toast.success(`Exported ${result.rows.length.toLocaleString("en-US")} log(s).`);
12632
+ closeExportModal();
12633
+ } catch (err) {
12634
+ if (controller.signal.aborted) return;
12635
+ setExportNotice(
12636
+ `Couldn't export usage logs. ${errorMessage(err, { method: "GET" })}`
12637
+ );
12638
+ } finally {
12639
+ if (exportAbortRef.current === controller) {
12640
+ exportAbortRef.current = null;
12641
+ setExportProgress(null);
12642
+ }
12098
12643
  }
12099
12644
  };
12100
12645
  function downloadCsv(rows, filename) {
12101
- const csv = rows.map(
12102
- (r) => r.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(",")
12103
- ).join("\n");
12646
+ const csv = toCsv(rows);
12104
12647
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
12105
12648
  const url = URL.createObjectURL(blob);
12106
12649
  const a = document.createElement("a");
@@ -12111,9 +12654,6 @@ function BillingContent({
12111
12654
  document.body.removeChild(a);
12112
12655
  URL.revokeObjectURL(url);
12113
12656
  }
12114
- function fmtFilename(d) {
12115
- return d.toISOString().slice(0, 10);
12116
- }
12117
12657
  async function handleBuyCredits(credits2, price) {
12118
12658
  if (!workspaceId) {
12119
12659
  toast.error("Missing workspace details for credit top-up");
@@ -12267,9 +12807,9 @@ function BillingContent({
12267
12807
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12268
12808
  import_ui17.ScPlanDetailsCard,
12269
12809
  {
12270
- planName,
12271
- planCredits: planCreditsText,
12272
- planPrice: planAmount,
12810
+ planName: sponsoredWithoutPlan ? "Free" : planName,
12811
+ planCredits: sponsoredWithoutPlan ? "Shared credits" : planCreditsText,
12812
+ planPrice: sponsoredWithoutPlan ? `${workspace.current?.country?.trim().toUpperCase() === "IN" ? "\u20B9" : "$"}0.00 / month` : planAmount,
12273
12813
  badgeText: "Active",
12274
12814
  badgeSubText: scheduledDowngradeText,
12275
12815
  currentPlanLabel: "Current plan",
@@ -12358,6 +12898,50 @@ function BillingContent({
12358
12898
  )
12359
12899
  ]
12360
12900
  }
12901
+ ) : sponsored && entitlement ? (
12902
+ // Billing says a pool entitles it, but the balance carried no
12903
+ // allowance (an older credits answer, or it failed): say whose.
12904
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
12905
+ "div",
12906
+ {
12907
+ role: "status",
12908
+ className: "flex flex-col shrink-0 w-full",
12909
+ style: {
12910
+ gap: 4,
12911
+ padding: 16,
12912
+ backgroundColor: "var(--alias-surface-base)",
12913
+ border: "1px solid var(--alias-border-subtle)",
12914
+ borderRadius: "var(--radius-3xl)"
12915
+ },
12916
+ children: [
12917
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12918
+ "span",
12919
+ {
12920
+ style: {
12921
+ fontFamily: "var(--font-family-font-family-body)",
12922
+ fontSize: "var(--font-size-font-size-sm)",
12923
+ fontWeight: 500,
12924
+ lineHeight: "var(--line-height-line-height-sm)",
12925
+ color: "var(--alias-text---icons-primary)"
12926
+ },
12927
+ children: sponsoredCreditsText(entitlement)
12928
+ }
12929
+ ),
12930
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12931
+ "span",
12932
+ {
12933
+ style: {
12934
+ fontFamily: "var(--font-family-font-family-body)",
12935
+ fontSize: "var(--font-size-font-size-sm)",
12936
+ lineHeight: "var(--line-height-line-height-sm)",
12937
+ color: "var(--alias-text---icons-muted)"
12938
+ },
12939
+ children: ownCreditsFirstText()
12940
+ }
12941
+ )
12942
+ ]
12943
+ }
12944
+ )
12361
12945
  ) : null,
12362
12946
  usage ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
12363
12947
  "div",
@@ -12522,22 +13106,39 @@ function BillingContent({
12522
13106
  loading: loadingTable,
12523
13107
  workspaceCountry: workspace.current?.country
12524
13108
  }
12525
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12526
- UsageLogsTable,
12527
- {
12528
- logs: usageLogs,
12529
- userMap,
12530
- loading: loadingTable,
12531
- currentWorkspaceId: workspaceId,
12532
- usedByNames
12533
- }
12534
- ),
13109
+ ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
13110
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13111
+ UsageLogsTable,
13112
+ {
13113
+ logs: usageLogs.rows,
13114
+ userMap,
13115
+ loading: loadingUsageLogs,
13116
+ error: usageLogsError,
13117
+ onRetry: () => void loadUsageLogsPage(usageLogsState.requestedPage),
13118
+ currentWorkspaceId: workspaceId,
13119
+ usedByNames
13120
+ }
13121
+ ),
13122
+ !usageLogsError && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
13123
+ UsageLogsPager,
13124
+ {
13125
+ page: usageLogs.page,
13126
+ totalPages: usageLogs.totalPages,
13127
+ total: usageLogs.total,
13128
+ loading: loadingUsageLogs,
13129
+ onPageChange: goToUsageLogsPage
13130
+ }
13131
+ )
13132
+ ] }),
12535
13133
  showExportModal && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
12536
13134
  ExportDateModal,
12537
13135
  {
12538
13136
  title: activeTab === "invoice" ? "Export invoices" : "Export log",
12539
- onClose: () => setShowExportModal(false),
12540
- onExport: handleExport
13137
+ onClose: closeExportModal,
13138
+ onExport: handleExport,
13139
+ busy: exportProgress !== null,
13140
+ busyText: exportProgress && exportProgress.total > 0 ? `Fetched ${exportProgress.fetched.toLocaleString("en-US")} of ${exportProgress.total.toLocaleString("en-US")} logs\u2026` : "Fetching logs\u2026",
13141
+ notice: exportNotice
12541
13142
  }
12542
13143
  ),
12543
13144
  showBuyCreditsModal && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
@@ -16732,6 +17333,30 @@ function OrganizationPicker({
16732
17333
  );
16733
17334
  }
16734
17335
 
17336
+ // src/use-billing-entitlement.ts
17337
+ var import_react30 = require("react");
17338
+ function useBillingEntitlement(fetchCurrentPlan, key) {
17339
+ const fetchRef = (0, import_react30.useRef)(fetchCurrentPlan);
17340
+ fetchRef.current = fetchCurrentPlan;
17341
+ const [state, setState] = (0, import_react30.useState)(null);
17342
+ const [loading, setLoading] = (0, import_react30.useState)(false);
17343
+ (0, import_react30.useEffect)(() => {
17344
+ const fetch2 = fetchRef.current;
17345
+ setState(null);
17346
+ if (!key || !fetch2) {
17347
+ setLoading(false);
17348
+ return;
17349
+ }
17350
+ let live = true;
17351
+ setLoading(true);
17352
+ loadCurrentPlan(fetch2).then((next) => live && setState(next)).catch(() => live && setState(null)).finally(() => live && setLoading(false));
17353
+ return () => {
17354
+ live = false;
17355
+ };
17356
+ }, [key]);
17357
+ return { state, loading };
17358
+ }
17359
+
16735
17360
  // src/administration-navigation.ts
16736
17361
  var normalizedBase = (base) => base.replace(/\/$/, "");
16737
17362
  var returnKey = (base, entryKey) => `streamoid:settings:return:${base}:${entryKey}`;
@@ -16778,7 +17403,7 @@ function readSettingsCaller(base, state, entryKey, fallback = `${normalizedBase(
16778
17403
  }
16779
17404
 
16780
17405
  // src/app-settings-page.tsx
16781
- var import_react30 = require("react");
17406
+ var import_react31 = require("react");
16782
17407
  var import_icons20 = require("@streamoid/icons");
16783
17408
 
16784
17409
  // src/app-settings-page.module.css
@@ -16841,7 +17466,7 @@ function AppSettingsSection({ title, action, children }) {
16841
17466
  ] });
16842
17467
  }
16843
17468
  function AppSettingsRow({ title, description, summary, href, onClick, disabled, disabledReason, ariaLabel, trailing }) {
16844
- const reasonId = (0, import_react30.useId)();
17469
+ const reasonId = (0, import_react31.useId)();
16845
17470
  const content = /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
16846
17471
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: app_settings_page_default.rowCopy, children: [
16847
17472
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: app_settings_page_default.rowTitle, children: title }),