@streamoid/settings 0.10.0 → 0.11.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
@@ -20,18 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- ALLOCATION_ERROR_CODES: () => ALLOCATION_ERROR_CODES,
24
- ALLOCATION_NOTE_MAX_LENGTH: () => ALLOCATION_NOTE_MAX_LENGTH,
25
23
  APP_KEYS: () => APP_KEYS,
26
24
  BillingContent: () => BillingContent,
27
25
  BuyCreditsModal: () => BuyCreditsModal,
28
- CANCEL_REASON_MAX_LENGTH: () => CANCEL_REASON_MAX_LENGTH,
29
- CancelCreditAllocationModal: () => CancelCreditAllocationModal,
30
26
  ConfirmDeleteModal: () => ConfirmDeleteModal,
31
27
  ConfirmLeaveModal: () => ConfirmLeaveModal,
32
28
  ConnectedAppsContent: () => ConnectedAppsContent,
33
- CreditAllocationModal: () => CreditAllocationModal,
34
- CreditAllocationsView: () => CreditAllocationsView,
29
+ CreditPoolMemberModal: () => CreditPoolMemberModal,
35
30
  DEFAULT_PERMISSIONS: () => DEFAULT_PERMISSIONS,
36
31
  DEFAULT_USER_LANGUAGE: () => DEFAULT_USER_LANGUAGE,
37
32
  ExportDateModal: () => ExportDateModal,
@@ -44,15 +39,20 @@ __export(index_exports, {
44
39
  OrganizationMemberModal: () => OrganizationMemberModal,
45
40
  OrganizationModal: () => OrganizationModal,
46
41
  OrganizationPicker: () => OrganizationPicker,
42
+ POOL_ERROR_CODES: () => POOL_ERROR_CODES,
43
+ POOL_NOTE_MAX_LENGTH: () => POOL_NOTE_MAX_LENGTH,
47
44
  PerUserLimitsView: () => PerUserLimitsView,
48
45
  ProfileAvatar: () => ProfileAvatar,
49
46
  ProfileTab: () => ProfileTab,
47
+ REMOVE_REASON_MAX_LENGTH: () => REMOVE_REASON_MAX_LENGTH,
50
48
  ReferralTab: () => ReferralTab,
49
+ RemovePoolMemberModal: () => RemovePoolMemberModal,
51
50
  SCOPE_LABELS: () => SCOPE_LABELS,
52
51
  ScopeHeader: () => ScopeHeader,
53
52
  SettingsAdaptersProvider: () => SettingsAdaptersProvider,
54
53
  SettingsContent: () => SettingsContent,
55
54
  SettingsTooltip: () => SettingsTooltip,
55
+ SharedCreditsView: () => SharedCreditsView,
56
56
  SiconKey: () => SiconKey_default,
57
57
  TAB_DESCRIPTIONS: () => TAB_DESCRIPTIONS,
58
58
  TAB_LABELS: () => TAB_LABELS,
@@ -68,28 +68,22 @@ __export(index_exports, {
68
68
  WorkspaceModal: () => WorkspaceModal,
69
69
  WorkspacePicker: () => WorkspacePicker,
70
70
  WorkspaceTab: () => WorkspaceTab,
71
- allocationErrorMessage: () => allocationErrorMessage,
72
- allocationWorkspaceName: () => allocationWorkspaceName,
73
71
  belowUsageReason: () => belowUsageReason,
74
- canShowCreditAllocations: () => canShowCreditAllocations,
75
- cancelOutcomeSummary: () => cancelOutcomeSummary,
76
- cancelOutcomeWarning: () => cancelOutcomeWarning,
77
- cleanAllocationValueInput: () => cleanAllocationValueInput,
72
+ canShowSharedCredits: () => canShowSharedCredits,
78
73
  cleanCreditLimitInput: () => cleanCreditLimitInput,
74
+ cleanPoolValueInput: () => cleanPoolValueInput,
79
75
  countLabel: () => countLabel,
80
76
  creditCycleUnit: () => creditCycleUnit,
81
- describeAllocation: () => describeAllocation,
77
+ creditsMeter: () => creditsMeter,
78
+ describeAllowance: () => describeAllowance,
82
79
  describeDefaultLimit: () => describeDefaultLimit,
83
80
  describeMemberLimit: () => describeMemberLimit,
84
- findAllocationError: () => findAllocationError,
85
81
  findBillingError: () => findBillingError,
86
- formatAllocationRepeat: () => formatAllocationRepeat,
87
- formatAllocationSplit: () => formatAllocationSplit,
82
+ findPoolError: () => findPoolError,
88
83
  formatCreditLimit: () => formatCreditLimit,
89
84
  formatCredits: () => formatCredits,
85
+ formatPoolSplit: () => formatPoolSplit,
90
86
  groupWorkspaceList: () => groupWorkspaceList,
91
- initialTransferSummary: () => initialTransferSummary,
92
- initialTransferWarning: () => initialTransferWarning,
93
87
  isLimitBelowUsage: () => isLimitBelowUsage,
94
88
  joinMembersWithLimits: () => joinMembersWithLimits,
95
89
  lastUsedLabel: () => lastUsedLabel,
@@ -98,15 +92,20 @@ __export(index_exports, {
98
92
  loadWorkspaceOrganization: () => loadWorkspaceOrganization,
99
93
  memberRoleUpdate: () => memberRoleUpdate,
100
94
  minimumCreditLimit: () => minimumCreditLimit,
95
+ poolErrorMessage: () => poolErrorMessage,
96
+ poolWorkspaceName: () => poolWorkspaceName,
101
97
  resolveSettingsTabFromPath: () => resolveSettingsTabFromPath,
102
98
  roundCredits: () => roundCredits,
99
+ sharedPoolBannerText: () => sharedPoolBannerText,
103
100
  useSettingsAdapters: () => useSettingsAdapters,
104
101
  useWorkspaceOrganization: () => useWorkspaceOrganization,
102
+ usedByNamesFrom: () => usedByNamesFrom,
103
+ usedByWorkspaceKey: () => usedByWorkspaceKey,
105
104
  userLanguageLabel: () => userLanguageLabel,
106
- validateAllocationText: () => validateAllocationText,
107
- validateAllocationValue: () => validateAllocationValue,
108
105
  validateCreditLimitInput: () => validateCreditLimitInput,
109
- validateDefaultCreditLimitInput: () => validateDefaultCreditLimitInput
106
+ validateDefaultCreditLimitInput: () => validateDefaultCreditLimitInput,
107
+ validatePoolText: () => validatePoolText,
108
+ validatePoolValue: () => validatePoolValue
110
109
  });
111
110
  module.exports = __toCommonJS(index_exports);
112
111
 
@@ -7215,6 +7214,8 @@ function OrganizationListContent() {
7215
7214
  const [createOpen, setCreateOpen] = (0, import_react13.useState)(false);
7216
7215
  const [managing, setManaging] = (0, import_react13.useState)(null);
7217
7216
  const [openingId, setOpeningId] = (0, import_react13.useState)(null);
7217
+ const [joinable, setJoinable] = (0, import_react13.useState)([]);
7218
+ const [joiningId, setJoiningId] = (0, import_react13.useState)(null);
7218
7219
  const organizationRef = (0, import_react13.useRef)(organization);
7219
7220
  organizationRef.current = organization;
7220
7221
  const load = (0, import_react13.useCallback)(async () => {
@@ -7227,6 +7228,32 @@ function OrganizationListContent() {
7227
7228
  setLoading(false);
7228
7229
  }
7229
7230
  }, []);
7231
+ const loadJoinable = (0, import_react13.useCallback)(async () => {
7232
+ const list = organizationRef.current.listJoinable;
7233
+ if (!list || !organizationRef.current.join) return;
7234
+ try {
7235
+ setJoinable(await list());
7236
+ } catch {
7237
+ setJoinable([]);
7238
+ }
7239
+ }, []);
7240
+ (0, import_react13.useEffect)(() => {
7241
+ void loadJoinable();
7242
+ }, [loadJoinable]);
7243
+ const joinOrganization = async (org) => {
7244
+ const join = organizationRef.current.join;
7245
+ if (!join) return;
7246
+ setJoiningId(org._id);
7247
+ try {
7248
+ await join(org._id);
7249
+ toast.success(`Joined "${org.name}". Open it to pick a workspace.`);
7250
+ await Promise.all([load(), loadJoinable()]);
7251
+ } catch (err) {
7252
+ toast.error(errorMessage(err, { method: "POST" }));
7253
+ } finally {
7254
+ setJoiningId(null);
7255
+ }
7256
+ };
7230
7257
  (0, import_react13.useEffect)(() => {
7231
7258
  void load();
7232
7259
  }, [organization.current?.id]);
@@ -7358,6 +7385,52 @@ function OrganizationListContent() {
7358
7385
  ))
7359
7386
  }
7360
7387
  ),
7388
+ joinable.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T12.spXl }, children: [
7389
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: T12.spXs }, children: [
7390
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { style: { margin: 0, color: T12.primary, fontSize: T12.fsLg, fontWeight: 600, lineHeight: T12.lhLg }, children: "Other organizations" }),
7391
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { style: { margin: 0, color: T12.muted, fontSize: T12.fsSm, lineHeight: T12.lhSm }, children: "Shared organizations at your company that you can join as a member." })
7392
+ ] }),
7393
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7394
+ "div",
7395
+ {
7396
+ className: "flex flex-col w-full",
7397
+ style: {
7398
+ background: T12.surface,
7399
+ border: `1px solid ${T12.borderSubtle}`,
7400
+ borderRadius: T12.r3xl
7401
+ },
7402
+ children: joinable.map((org, index) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
7403
+ "div",
7404
+ {
7405
+ className: "flex items-center w-full",
7406
+ style: {
7407
+ gap: T12.spXl,
7408
+ padding: `${T12.spXl} ${T12.sp3xl}`,
7409
+ borderTop: index ? `1px solid ${T12.borderSubtle}` : "none"
7410
+ },
7411
+ children: [
7412
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ProfileAvatar, { name: org.name, profileImage: org.imageUrl, size: 36, borderRadius: T12.rLg }),
7413
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col flex-1 min-w-0", children: [
7414
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate", style: { color: T12.primary, fontSize: T12.fsSm, fontWeight: 500, lineHeight: T12.lhSm }, children: org.name }),
7415
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate", style: { color: T12.muted, fontSize: T12.fsXs, lineHeight: T12.lhXs }, children: ["Shared", org.domain].filter(Boolean).join(" \xB7 ") })
7416
+ ] }),
7417
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7418
+ import_ui9.ScButton,
7419
+ {
7420
+ text: joiningId === org._id ? "Joining\u2026" : "Join",
7421
+ variant: "secondary",
7422
+ size: "sm",
7423
+ state: joiningId ? "disabled" : "default",
7424
+ onClick: joiningId ? void 0 : () => void joinOrganization(org)
7425
+ }
7426
+ )
7427
+ ]
7428
+ },
7429
+ org._id
7430
+ ))
7431
+ }
7432
+ )
7433
+ ] }),
7361
7434
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7362
7435
  OrganizationModal,
7363
7436
  {
@@ -7958,14 +8031,14 @@ function BuyCreditsModal({
7958
8031
  const maxCredits = Math.round(
7959
8032
  topupConfig.maxAmount / topupConfig.pricePerCredit
7960
8033
  );
7961
- const [credits, setCredits] = (0, import_react15.useState)(minCredits);
8034
+ const [credits2, setCredits] = (0, import_react15.useState)(minCredits);
7962
8035
  const [creditsInput, setCreditsInput] = (0, import_react15.useState)(String(minCredits));
7963
8036
  const [priceInput, setPriceInput] = (0, import_react15.useState)(
7964
8037
  String(minCredits * topupConfig.pricePerCredit)
7965
8038
  );
7966
8039
  const [activeField, setActiveField] = (0, import_react15.useState)("credits");
7967
- const parsedCredits = creditsInput.length ? Number(creditsInput) : credits;
7968
- const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits;
8040
+ const parsedCredits = creditsInput.length ? Number(creditsInput) : credits2;
8041
+ const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits2;
7969
8042
  const pricedCredits = previewCredits > 0 ? previewCredits : 0;
7970
8043
  const price = Number(
7971
8044
  (pricedCredits * topupConfig.pricePerCredit).toFixed(2)
@@ -7987,8 +8060,8 @@ function BuyCreditsModal({
7987
8060
  setCreditsInput(String(derived));
7988
8061
  setPriceInput(String(amount));
7989
8062
  };
7990
- const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits - STEP));
7991
- const handleIncrement = () => syncFromCredits(Math.min(maxCredits, credits + STEP));
8063
+ const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits2 - STEP));
8064
+ const handleIncrement = () => syncFromCredits(Math.min(maxCredits, credits2 + STEP));
7992
8065
  const handleCreditsChange = (value) => {
7993
8066
  const raw = value.replace(/[^\d]/g, "");
7994
8067
  setCreditsInput(raw);
@@ -8122,7 +8195,7 @@ function BuyCreditsModal({
8122
8195
  style: {
8123
8196
  width: 24,
8124
8197
  height: 24,
8125
- opacity: credits <= minCredits ? 0.3 : 1,
8198
+ opacity: credits2 <= minCredits ? 0.3 : 1,
8126
8199
  transition: "opacity 0.15s ease"
8127
8200
  },
8128
8201
  onClick: handleDecrement,
@@ -8180,7 +8253,7 @@ function BuyCreditsModal({
8180
8253
  style: {
8181
8254
  width: 24,
8182
8255
  height: 24,
8183
- opacity: credits >= maxCredits ? 0.3 : 1,
8256
+ opacity: credits2 >= maxCredits ? 0.3 : 1,
8184
8257
  transition: "opacity 0.15s ease"
8185
8258
  },
8186
8259
  onClick: handleIncrement,
@@ -8312,7 +8385,7 @@ function BuyCreditsModal({
8312
8385
  pointerEvents: isInvalid ? "none" : "auto",
8313
8386
  transition: "opacity 0.2s ease"
8314
8387
  },
8315
- onClick: () => onBuy(credits, price),
8388
+ onClick: () => onBuy(credits2, price),
8316
8389
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8317
8390
  "span",
8318
8391
  {
@@ -9052,7 +9125,7 @@ function PerUserLimitsView() {
9052
9125
  ] });
9053
9126
  }
9054
9127
 
9055
- // src/credit-allocations-view.tsx
9128
+ // src/shared-credits-view.tsx
9056
9129
  var import_react21 = require("react");
9057
9130
  var import_ui15 = require("@streamoid/ui");
9058
9131
 
@@ -9301,23 +9374,23 @@ function ConfirmActionModal({
9301
9374
  );
9302
9375
  }
9303
9376
 
9304
- // src/credit-allocation-modal.tsx
9377
+ // src/credit-pool-modal.tsx
9305
9378
  var import_react19 = require("react");
9306
9379
  var import_ui13 = require("@streamoid/ui");
9307
9380
  var import_icons13 = require("@streamoid/icons");
9308
9381
 
9309
- // src/credit-allocations.ts
9310
- var ALLOCATION_NOTE_MAX_LENGTH = 500;
9311
- var CANCEL_REASON_MAX_LENGTH = 500;
9312
- function canShowCreditAllocations(api, workspace, organization) {
9313
- return Boolean(api.fetchCreditAllocations) && Boolean(workspace?.organizationId || organization?.id);
9382
+ // src/credit-pool.ts
9383
+ var POOL_NOTE_MAX_LENGTH = 500;
9384
+ var REMOVE_REASON_MAX_LENGTH = 500;
9385
+ function canShowSharedCredits(api, workspace, organization) {
9386
+ return Boolean(api.fetchCreditPool) && Boolean(workspace?.organizationId || organization?.id);
9314
9387
  }
9315
- function cleanAllocationValueInput(raw) {
9388
+ function cleanPoolValueInput(raw) {
9316
9389
  const kept = raw.replace(/[^\d.]/g, "");
9317
9390
  const dot = kept.indexOf(".");
9318
9391
  return dot === -1 ? kept : kept.slice(0, dot + 1) + kept.slice(dot + 1).replace(/\./g, "");
9319
9392
  }
9320
- function validateAllocationValue(mode, raw, balance) {
9393
+ function validatePoolValue(mode, raw) {
9321
9394
  const trimmed = raw.trim();
9322
9395
  if (!trimmed) return { ok: false, reason: mode === "percent" ? "Enter a percentage" : "Enter a number of credits" };
9323
9396
  if (!/^\d+(\.\d{1,2})?$/.test(trimmed)) {
@@ -9326,64 +9399,61 @@ function validateAllocationValue(mode, raw, balance) {
9326
9399
  const value = Number(trimmed);
9327
9400
  if (!(value > 0)) return { ok: false, reason: "Enter a number greater than 0" };
9328
9401
  if (mode === "percent" && value > 100) return { ok: false, reason: "A percentage can be at most 100" };
9329
- if (mode === "fixed" && typeof balance === "number" && Number.isFinite(balance) && value > balance) {
9330
- return {
9331
- ok: false,
9332
- reason: `This workspace has ${formatCredits(balance)} credits available`
9333
- };
9334
- }
9335
9402
  return { ok: true, value };
9336
9403
  }
9337
- function validateAllocationText(raw, max) {
9404
+ function validatePoolText(raw, max) {
9338
9405
  const value = raw.trim();
9339
9406
  if (!value) return { ok: false, reason: "Required" };
9340
9407
  if (value.length > max) return { ok: false, reason: `At most ${max} characters` };
9341
9408
  return { ok: true, value };
9342
9409
  }
9343
- function formatAllocationSplit(split) {
9410
+ var credits = (value) => `${formatCredits(value)} ${value === 1 ? "credit" : "credits"}`;
9411
+ function formatPoolSplit(split) {
9344
9412
  if (split.mode === "percent") return `${formatCredits(split.value)}%`;
9345
- return `${formatCredits(split.value)} ${split.value === 1 ? "credit" : "credits"}`;
9413
+ return credits(split.value);
9346
9414
  }
9347
- function formatAllocationRepeat(recurring) {
9348
- return recurring ? "Every renewal" : "Once";
9415
+ function describeAllowance(member) {
9416
+ const allowance = `${credits(member.allowance)} this cycle`;
9417
+ return member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 ${allowance}` : allowance;
9349
9418
  }
9350
- function describeAllocation(allocation) {
9351
- const split = formatAllocationSplit(allocation);
9352
- if (!allocation.recurring) return `${split} once`;
9353
- return allocation.mode === "percent" ? `${split} of each renewal` : `${split} every renewal`;
9354
- }
9355
- function allocationWorkspaceName(name) {
9419
+ function poolWorkspaceName(name) {
9356
9420
  return name?.trim() || "Unknown workspace";
9357
9421
  }
9358
- function initialTransferWarning(allocation) {
9359
- const transfer = allocation.initialTransfer;
9360
- if (transfer?.status !== "failed") return null;
9361
- const why = transfer.error?.trim();
9362
- return `The first transfer failed${why ? `: ${why}` : ""}. The allocation is active and renewals will still split, but these credits did not move.`;
9363
- }
9364
- function initialTransferSummary(allocation) {
9365
- const transfer = allocation.initialTransfer;
9366
- if (transfer?.status !== "done") return null;
9367
- return `Sent ${formatCredits(transfer.credits)} ${transfer.credits === 1 ? "credit" : "credits"} at setup`;
9368
- }
9369
- function cancelOutcomeWarning(result, targetName) {
9370
- const stuck = !result.targetMovedToFree && !result.reason?.trim() && result.allocation?.targetPlanChanged === true;
9371
- if (!result.error && !stuck) return null;
9372
- return `Allocation canceled, but ${targetName} is still on the $0 Custom plan and won't get its free monthly credits. Contact Streamoid support with the workspace name ${targetName} and the ID below to move it back to the free plan.`;
9373
- }
9374
- function cancelOutcomeSummary(result, targetName) {
9375
- const why = result.reason?.trim();
9376
- if (result.targetMovedToFree) return `Allocation canceled. ${targetName} is back on the free plan.`;
9377
- return why ? `Allocation canceled. ${why.replace(/\.?$/, ".")}` : "Allocation canceled";
9378
- }
9379
- var ALLOCATION_ERROR_CODES = [
9380
- "INSUFFICIENT_CREDITS",
9381
- "SOURCE_ON_FREE_PLAN",
9382
- "ALLOCATION_NOT_ALLOWED",
9383
- "TARGET_PLAN_CHANGE_FAILED"
9384
- ];
9385
- function findAllocationError(failure) {
9386
- const error = findBillingError(failure, ALLOCATION_ERROR_CODES);
9422
+ function sharedPoolBannerText(pool) {
9423
+ const sponsor = pool.sponsorName?.trim() || "another workspace";
9424
+ return `Credits shared by ${sponsor}: ${formatCredits(pool.remaining)} of ${formatCredits(pool.allowance)} left this cycle`;
9425
+ }
9426
+ function creditsMeter({
9427
+ availableCredits,
9428
+ totalCredits,
9429
+ sharedPool
9430
+ }) {
9431
+ if (sharedPool) {
9432
+ const used2 = Math.max(0, sharedPool.used);
9433
+ const remaining = Math.max(0, sharedPool.remaining);
9434
+ const progressPct2 = sharedPool.allowance > 0 ? Math.min(100, Math.round(used2 / sharedPool.allowance * 100)) : 100;
9435
+ return { used: used2, remaining, progressPct: progressPct2, remainingPct: 100 - progressPct2 };
9436
+ }
9437
+ const used = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
9438
+ const progressPct = used != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(used / totalCredits * 100)) : 0;
9439
+ return { used, remaining: availableCredits, progressPct, remainingPct: 100 - progressPct };
9440
+ }
9441
+ function usedByWorkspaceKey(log, currentWorkspaceId) {
9442
+ const usedBy = log.meta?.usedBy;
9443
+ if (typeof usedBy !== "string" || !usedBy) return null;
9444
+ return usedBy.toLowerCase() === String(currentWorkspaceId ?? "").toLowerCase() ? null : usedBy;
9445
+ }
9446
+ function usedByNamesFrom(pool) {
9447
+ const names = {};
9448
+ for (const member of [...pool?.formerMembers ?? [], ...pool?.members ?? []]) {
9449
+ const name = member.memberWorkspaceName?.trim();
9450
+ if (name && member.memberWorkspaceKey) names[member.memberWorkspaceKey.toLowerCase()] = name;
9451
+ }
9452
+ return names;
9453
+ }
9454
+ var POOL_ERROR_CODES = ["SPONSOR_ON_FREE_PLAN", "POOL_NOT_ALLOWED"];
9455
+ function findPoolError(failure) {
9456
+ const error = findBillingError(failure, POOL_ERROR_CODES);
9387
9457
  if (!error) return null;
9388
9458
  const status = error.status;
9389
9459
  const message = [status?.message, error.error, error.message].find(
@@ -9392,29 +9462,18 @@ function findAllocationError(failure) {
9392
9462
  const details = error.details && typeof error.details === "object" ? error.details : {};
9393
9463
  return { code: error.code, details, message: message ?? null };
9394
9464
  }
9395
- var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
9396
- function allocationErrorMessage(failure) {
9397
- const found = findAllocationError(failure);
9465
+ function poolErrorMessage(failure) {
9466
+ const found = findPoolError(failure);
9398
9467
  if (!found) return null;
9399
9468
  switch (found.code) {
9400
- case "INSUFFICIENT_CREDITS": {
9401
- const balance = num(found.details.currentBalance);
9402
- const required = num(found.details.requiredCredits);
9403
- if (balance !== null && required !== null) {
9404
- return `Not enough credits: this would send ${formatCredits(required)} now, but the workspace has ${formatCredits(balance)}.`;
9405
- }
9406
- return "This workspace doesn't have enough credits to send that now.";
9407
- }
9408
- case "SOURCE_ON_FREE_PLAN":
9469
+ case "SPONSOR_ON_FREE_PLAN":
9409
9470
  return "A workspace on the free plan can't share credits. Upgrade this workspace's plan first.";
9410
- case "ALLOCATION_NOT_ALLOWED":
9411
- return found.message ? found.message.replace(/\.?$/, ".") : "This allocation isn't allowed.";
9412
- case "TARGET_PLAN_CHANGE_FAILED":
9413
- return found.details.allocationId ? "The target workspace moved to the Custom plan, but its plan details weren't saved. Cancel this allocation to move it back to free." : "Couldn't move the target workspace to the Custom plan, so nothing was transferred. Try again.";
9471
+ case "POOL_NOT_ALLOWED":
9472
+ return found.message ? found.message.replace(/\.?$/, ".") : "Credits can't be shared with that workspace.";
9414
9473
  }
9415
9474
  }
9416
9475
 
9417
- // src/credit-allocation-modal.tsx
9476
+ // src/credit-pool-modal.tsx
9418
9477
  var import_jsx_runtime25 = require("react/jsx-runtime");
9419
9478
  var MODAL_T = {
9420
9479
  primary: "var(--alias-text---icons-primary)",
@@ -9447,7 +9506,7 @@ var modalBodyText = {
9447
9506
  fontSize: T16.fsSm,
9448
9507
  lineHeight: T16.lhSm
9449
9508
  };
9450
- function AllocationModalShell({
9509
+ function PoolModalShell({
9451
9510
  title,
9452
9511
  busy,
9453
9512
  onClose,
@@ -9548,7 +9607,7 @@ function AllocationModalShell({
9548
9607
  }
9549
9608
  );
9550
9609
  }
9551
- function AllocationWarning({ children }) {
9610
+ function PoolWarning({ children }) {
9552
9611
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9553
9612
  "div",
9554
9613
  {
@@ -9567,68 +9626,55 @@ function AllocationWarning({ children }) {
9567
9626
  );
9568
9627
  }
9569
9628
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9570
- function CreditAllocationModal(props) {
9629
+ function CreditPoolMemberModal(props) {
9571
9630
  const { toast } = useSettingsAdapters();
9572
- const editing = props.kind === "edit" ? props.allocation : null;
9573
- const [targetId, setTargetId] = (0, import_react19.useState)("");
9574
- const [mode, setMode] = (0, import_react19.useState)(editing?.mode ?? "fixed");
9631
+ const editing = props.kind === "edit" ? props.member : null;
9632
+ const [memberId, setMemberId] = (0, import_react19.useState)("");
9633
+ const [mode, setMode] = (0, import_react19.useState)(editing?.mode ?? "percent");
9575
9634
  const [raw, setRaw] = (0, import_react19.useState)(editing ? String(editing.value) : "");
9576
- const [recurring, setRecurring] = (0, import_react19.useState)(editing?.recurring ?? true);
9577
9635
  const [note, setNote] = (0, import_react19.useState)("");
9578
9636
  const [touched, setTouched] = (0, import_react19.useState)(false);
9579
9637
  const [saving, setSaving] = (0, import_react19.useState)(false);
9580
9638
  const [serverError, setServerError] = (0, import_react19.useState)(null);
9581
9639
  (0, import_react19.useEffect)(() => {
9582
9640
  setServerError(null);
9583
- }, [targetId, mode, raw, recurring, note]);
9584
- const balance = props.kind === "create" ? props.balance : null;
9585
- const value = validateAllocationValue(mode, raw, props.kind === "create" ? balance : null);
9586
- const noteCheck = validateAllocationText(note, ALLOCATION_NOTE_MAX_LENGTH);
9587
- const splitChanged = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9588
- const changed = !editing || splitChanged || recurring !== editing.recurring;
9589
- const valid = value.ok && (props.kind === "edit" || Boolean(targetId) && noteCheck.ok);
9641
+ }, [memberId, mode, raw, note]);
9642
+ const value = validatePoolValue(mode, raw);
9643
+ const noteCheck = validatePoolText(note, POOL_NOTE_MAX_LENGTH);
9644
+ const changed = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9645
+ const valid = value.ok && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9590
9646
  const canSave = !saving && changed && valid;
9591
9647
  const valueHint = touched && !value.ok ? value.reason : null;
9592
- const percentNow = props.kind === "create" && mode === "percent" && value.ok && balance !== null ? Math.floor(roundCredits(balance * value.value / 100) * 100) / 100 : null;
9593
9648
  const handleSave = async () => {
9594
9649
  if (!canSave || !value.ok) return;
9595
9650
  setSaving(true);
9596
9651
  try {
9597
- if (props.kind === "create") {
9598
- const created = await props.onSave({
9599
- targetWorkspaceId: targetId,
9652
+ if (props.kind === "add") {
9653
+ const added = await props.onSave({
9654
+ memberWorkspaceId: memberId,
9600
9655
  mode,
9601
9656
  value: value.value,
9602
- recurring,
9603
9657
  note: noteCheck.ok ? noteCheck.value : ""
9604
9658
  });
9605
- const warning = initialTransferWarning(created);
9606
- if (warning) toast.error(warning);
9607
- else {
9608
- const sent = initialTransferSummary(created);
9609
- toast.success(sent ? `Allocation created. ${sent}.` : "Allocation created");
9610
- }
9659
+ toast.success(`Credits shared. ${describeAllowance(added)}.`);
9611
9660
  } else {
9612
- await props.onSave(
9613
- splitChanged ? { mode, value: value.value, recurring } : { recurring }
9614
- );
9615
- toast.success("Allocation updated. Applies from the next renewal.");
9661
+ const updated = await props.onSave({ mode, value: value.value });
9662
+ toast.success(`Share updated. ${describeAllowance(updated)}.`);
9616
9663
  }
9617
9664
  props.onSaved();
9618
9665
  props.onClose();
9619
9666
  } catch (err) {
9620
- const message = allocationErrorMessage(err);
9667
+ const message = poolErrorMessage(err);
9621
9668
  if (message) setServerError(message);
9622
- else toast.error(errorMessage(err, { method: props.kind === "create" ? "POST" : "PATCH" }));
9623
- if (findAllocationError(err)?.details.allocationId) props.onSaved();
9669
+ else toast.error(errorMessage(err, { method: props.kind === "add" ? "POST" : "PATCH" }));
9624
9670
  setSaving(false);
9625
9671
  }
9626
9672
  };
9627
- const targetOptions = props.kind === "create" ? props.targets.map((target) => ({ id: target.id, label: target.name })) : [];
9673
+ const candidateOptions = props.kind === "add" ? props.candidates.map((candidate) => ({ id: candidate.id, label: candidate.name })) : [];
9628
9674
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
9629
- AllocationModalShell,
9675
+ PoolModalShell,
9630
9676
  {
9631
- title: props.kind === "create" ? "New credit allocation" : "Edit credit allocation",
9677
+ title: props.kind === "add" ? "Share credits" : "Edit shared credits",
9632
9678
  busy: saving,
9633
9679
  onClose: props.onClose,
9634
9680
  footer: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
@@ -9645,7 +9691,7 @@ function CreditAllocationModal(props) {
9645
9691
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9646
9692
  import_ui13.ScButton,
9647
9693
  {
9648
- text: props.kind === "create" ? "Create allocation" : "Save",
9694
+ text: props.kind === "add" ? "Share credits" : "Save",
9649
9695
  variant: "primary",
9650
9696
  size: "md",
9651
9697
  state: canSave ? "default" : "disabled",
@@ -9655,29 +9701,29 @@ function CreditAllocationModal(props) {
9655
9701
  ] }),
9656
9702
  children: [
9657
9703
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9658
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Send credits to" }),
9659
- props.kind === "create" ? targetOptions.length ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9704
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Share with" }),
9705
+ props.kind === "add" ? candidateOptions.length ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9660
9706
  import_ui13.ScSelect,
9661
9707
  {
9662
- value: targetId,
9663
- onChange: setTargetId,
9664
- options: targetOptions,
9708
+ value: memberId,
9709
+ onChange: setMemberId,
9710
+ options: candidateOptions,
9665
9711
  placeholder: "Choose a workspace",
9666
9712
  disabled: saving
9667
9713
  }
9668
- ) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.muted }, children: "There are no other workspaces in this organization to send credits to." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.primary }, children: props.targetName })
9714
+ ) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.muted }, children: "There are no other workspaces in this organization to share credits with." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.primary }, children: props.memberName })
9669
9715
  ] }),
9670
9716
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spMd }, children: [
9671
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "How much" }),
9717
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "How much, each billing cycle" }),
9672
9718
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-center", style: { gap: T16.sp3xl }, children: [
9673
9719
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9674
9720
  import_ui13.ScPairtext,
9675
9721
  {
9676
9722
  type: "radio",
9677
- content: "Fixed credits",
9678
- checked: mode === "fixed",
9723
+ content: "Percentage",
9724
+ checked: mode === "percent",
9679
9725
  onChange: () => {
9680
- if (!saving) setMode("fixed");
9726
+ if (!saving) setMode("percent");
9681
9727
  }
9682
9728
  }
9683
9729
  ),
@@ -9685,10 +9731,10 @@ function CreditAllocationModal(props) {
9685
9731
  import_ui13.ScPairtext,
9686
9732
  {
9687
9733
  type: "radio",
9688
- content: "Percentage",
9689
- checked: mode === "percent",
9734
+ content: "Fixed credits",
9735
+ checked: mode === "fixed",
9690
9736
  onChange: () => {
9691
- if (!saving) setMode("percent");
9737
+ if (!saving) setMode("fixed");
9692
9738
  }
9693
9739
  }
9694
9740
  )
@@ -9716,7 +9762,7 @@ function CreditAllocationModal(props) {
9716
9762
  value: raw,
9717
9763
  disabled: saving,
9718
9764
  onChange: (e) => {
9719
- setRaw(cleanAllocationValueInput(e.target.value));
9765
+ setRaw(cleanPoolValueInput(e.target.value));
9720
9766
  setTouched(true);
9721
9767
  },
9722
9768
  className: "flex-1 min-w-0",
@@ -9734,39 +9780,18 @@ function CreditAllocationModal(props) {
9734
9780
  ]
9735
9781
  }
9736
9782
  ),
9737
- valueHint ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : props.kind === "create" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.tertiary }, children: percentNow !== null ? `Sends ${formatCredits(percentNow)} credits now, out of ${formatCredits(balance ?? 0)} available.` : balance !== null ? `Sent now from this workspace's ${formatCredits(balance)} available credits.` : "Sent now from this workspace's available credits." }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.tertiary }, children: "Changes apply from the next renewal; credits already sent stay where they are." })
9738
- ] }),
9739
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-start justify-between w-full", style: { gap: T16.sp3xl }, children: [
9740
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: T16.spXs }, children: [
9741
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Repeat on every renewal" }),
9742
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { ...modalBodyText, color: T16.muted }, children: mode === "percent" ? "Also send this share of each renewal grant this workspace receives." : "Also send these credits out of each renewal grant this workspace receives." })
9743
- ] }),
9744
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { display: "inline-flex", width: 41, height: 25, alignItems: "center", flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9745
- import_ui13.ScToggleSwitch,
9746
- {
9747
- checked: recurring,
9748
- disabled: saving,
9749
- onToggle: (next) => setRecurring(next),
9750
- role: "switch",
9751
- "aria-checked": recurring,
9752
- "aria-label": "Repeat on every renewal",
9753
- tabIndex: 0,
9754
- onKeyDown: (event) => {
9755
- if ((event.key === " " || event.key === "Enter") && !saving) {
9756
- event.preventDefault();
9757
- setRecurring(!recurring);
9758
- }
9759
- }
9760
- }
9761
- ) })
9783
+ valueHint ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { style: { ...modalBodyText, color: T16.tertiary }, children: [
9784
+ mode === "percent" ? "A share of this workspace's plan credits for each billing cycle. Credits stay with this workspace; the other workspace spends them up to this share." : "Credits the other workspace may spend each billing cycle. Credits stay with this workspace until spent.",
9785
+ editing ? " Applies immediately." : ""
9786
+ ] })
9762
9787
  ] }),
9763
- props.kind === "create" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9788
+ props.kind === "add" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9764
9789
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Note" }),
9765
9790
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9766
9791
  import_ui13.ScTextArea,
9767
9792
  {
9768
9793
  value: note,
9769
- maxLength: ALLOCATION_NOTE_MAX_LENGTH,
9794
+ maxLength: POOL_NOTE_MAX_LENGTH,
9770
9795
  placeholder: "Why are these credits being shared?",
9771
9796
  disabled: saving,
9772
9797
  onChange: (e) => setNote(e.target.value),
@@ -9778,117 +9803,57 @@ function CreditAllocationModal(props) {
9778
9803
  "Required. ",
9779
9804
  note.trim().length,
9780
9805
  "/",
9781
- ALLOCATION_NOTE_MAX_LENGTH
9806
+ POOL_NOTE_MAX_LENGTH
9782
9807
  ] })
9783
9808
  ] }),
9784
- props.kind === "create" && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AllocationWarning, { children: "A workspace on the free plan moves to a $0 Custom plan funded by this workspace. Cancelling moves it back to free." }),
9809
+ props.kind === "add" && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PoolWarning, { children: "While it spends shared credits, the other workspace uses only this workspace's credits, not its own." }),
9785
9810
  serverError && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
9786
9811
  ]
9787
9812
  }
9788
9813
  );
9789
9814
  }
9790
9815
 
9791
- // src/cancel-credit-allocation-modal.tsx
9816
+ // src/remove-pool-member-modal.tsx
9792
9817
  var import_react20 = require("react");
9793
9818
  var import_ui14 = require("@streamoid/ui");
9794
9819
  var import_jsx_runtime26 = require("react/jsx-runtime");
9795
- function CancelCreditAllocationModal({
9796
- allocation,
9797
- targetName,
9798
- onCancel,
9820
+ function RemovePoolMemberModal({
9821
+ member,
9822
+ memberName,
9823
+ sponsorName,
9824
+ onRemove,
9799
9825
  onClose,
9800
- onCanceled
9826
+ onRemoved
9801
9827
  }) {
9802
9828
  const { toast } = useSettingsAdapters();
9803
9829
  const [reason, setReason] = (0, import_react20.useState)("");
9804
9830
  const [saving, setSaving] = (0, import_react20.useState)(false);
9805
- const [outcome, setOutcome] = (0, import_react20.useState)(null);
9806
- const check = validateAllocationText(reason, CANCEL_REASON_MAX_LENGTH);
9831
+ const check = validatePoolText(reason, REMOVE_REASON_MAX_LENGTH);
9807
9832
  const canConfirm = !saving && check.ok;
9808
9833
  const handleConfirm = async () => {
9809
9834
  if (!canConfirm || !check.ok) return;
9810
9835
  setSaving(true);
9811
9836
  try {
9812
- const result = await onCancel(check.value);
9813
- const warning = cancelOutcomeWarning(
9814
- { ...result, allocation: result.allocation ?? allocation },
9815
- targetName
9816
- );
9817
- onCanceled({ planNotRestored: Boolean(warning) });
9818
- if (warning) {
9819
- setOutcome({ warning, error: result.error });
9820
- setSaving(false);
9821
- return;
9822
- }
9823
- toast.success(cancelOutcomeSummary(result, targetName));
9837
+ await onRemove(check.value);
9838
+ toast.success(`${memberName} can no longer spend ${sponsorName}'s credits`);
9839
+ onRemoved();
9824
9840
  onClose();
9825
9841
  } catch (err) {
9826
9842
  toast.error(errorMessage(err, { method: "DELETE" }));
9827
9843
  setSaving(false);
9828
9844
  }
9829
9845
  };
9830
- const copyId = () => {
9831
- const id = allocation.targetWorkspaceKey;
9832
- const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
9833
- if (!clipboard) {
9834
- toast.error("Couldn't copy. Select the ID and copy it instead.");
9835
- return;
9836
- }
9837
- clipboard.writeText(id).then(
9838
- () => toast.success("Workspace ID copied"),
9839
- () => toast.error("Couldn't copy. Select the ID and copy it instead.")
9840
- );
9841
- };
9842
- if (outcome) {
9843
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
9844
- AllocationModalShell,
9845
- {
9846
- title: "Target plan not restored",
9847
- busy: false,
9848
- onClose,
9849
- footer: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui14.ScButton, { text: "Got it", variant: "primary", size: "md", onClick: onClose }),
9850
- children: [
9851
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AllocationWarning, { children: outcome.warning }),
9852
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9853
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Workspace ID" }),
9854
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center w-full", style: { gap: MODAL_T.spXl }, children: [
9855
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9856
- "code",
9857
- {
9858
- className: "flex-1 min-w-0 truncate",
9859
- style: {
9860
- ...modalBodyText,
9861
- color: MODAL_T.primary,
9862
- background: MODAL_T.fillPlus,
9863
- borderRadius: MODAL_T.r3xl,
9864
- padding: `${MODAL_T.spXl} ${MODAL_T.sp3xl}`,
9865
- userSelect: "all"
9866
- },
9867
- children: allocation.targetWorkspaceKey
9868
- }
9869
- ),
9870
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui14.ScButton, { text: "Copy", variant: "secondary", size: "sm", onClick: copyId })
9871
- ] })
9872
- ] }),
9873
- outcome.error && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: MODAL_T.muted }, children: [
9874
- "Details: ",
9875
- outcome.error
9876
- ] })
9877
- ]
9878
- }
9879
- );
9880
- }
9881
9846
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
9882
- AllocationModalShell,
9847
+ PoolModalShell,
9883
9848
  {
9884
- title: "Cancel credit allocation",
9849
+ title: "Stop sharing credits",
9885
9850
  busy: saving,
9886
9851
  onClose,
9887
9852
  footer: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
9888
9853
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9889
9854
  import_ui14.ScButton,
9890
9855
  {
9891
- text: "Keep allocation",
9856
+ text: "Keep sharing",
9892
9857
  variant: "secondary",
9893
9858
  size: "md",
9894
9859
  state: saving ? "disabled" : "default",
@@ -9898,7 +9863,7 @@ function CancelCreditAllocationModal({
9898
9863
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9899
9864
  import_ui14.ScButton,
9900
9865
  {
9901
- text: "Cancel allocation",
9866
+ text: "Stop sharing",
9902
9867
  variant: "error",
9903
9868
  size: "md",
9904
9869
  state: canConfirm ? "default" : "disabled",
@@ -9908,16 +9873,18 @@ function CancelCreditAllocationModal({
9908
9873
  ] }),
9909
9874
  children: [
9910
9875
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: MODAL_T.primary }, children: [
9911
- "Stop sending ",
9912
- describeAllocation(allocation),
9913
- " to ",
9914
- targetName,
9915
- "?"
9876
+ "This stops ",
9877
+ memberName,
9878
+ " spending ",
9879
+ sponsorName,
9880
+ "'s credits immediately."
9916
9881
  ] }),
9917
9882
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: MODAL_T.tertiary }, children: [
9918
- targetName,
9919
- " keeps the credits it already received until they expire.",
9920
- allocation.targetPlanChanged ? " It moved to a $0 Custom plan for this allocation, so it goes back to the free plan now, unless it has since moved to a plan of its own." : ""
9883
+ "It had ",
9884
+ describeAllowance(member),
9885
+ " and has used ",
9886
+ formatCredits(member.used),
9887
+ ". Credits it already spent are not returned; anything it runs from now uses its own credits."
9921
9888
  ] }),
9922
9889
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9923
9890
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Reason" }),
@@ -9925,8 +9892,8 @@ function CancelCreditAllocationModal({
9925
9892
  import_ui14.ScTextArea,
9926
9893
  {
9927
9894
  value: reason,
9928
- maxLength: CANCEL_REASON_MAX_LENGTH,
9929
- placeholder: "Why is this allocation ending?",
9895
+ maxLength: REMOVE_REASON_MAX_LENGTH,
9896
+ placeholder: "Why is this workspace no longer sharing these credits?",
9930
9897
  disabled: saving,
9931
9898
  onChange: (e) => setReason(e.target.value),
9932
9899
  rows: 3,
@@ -9940,16 +9907,13 @@ function CancelCreditAllocationModal({
9940
9907
  );
9941
9908
  }
9942
9909
 
9943
- // src/credit-allocations-view.tsx
9910
+ // src/shared-credits-view.tsx
9944
9911
  var import_jsx_runtime27 = require("react/jsx-runtime");
9945
9912
  var T17 = {
9946
9913
  primary: "var(--alias-text---icons-primary)",
9947
9914
  muted: "var(--alias-text---icons-muted)",
9948
9915
  tertiary: "var(--alias-text---icons-tertiary)",
9949
9916
  warning: "var(--alias-text---icons-warning)",
9950
- success: "var(--alias-text---icons-success)",
9951
- warningFill: "var(--alias-fill-warning-soft)",
9952
- warningBorder: "var(--alias-border-warning)",
9953
9917
  fillPlus: "var(--alias-fill-neutral-neutralplus)",
9954
9918
  surfaceBase: "var(--alias-surface-base)",
9955
9919
  borderSubtle: "var(--alias-border-subtle)",
@@ -9963,9 +9927,7 @@ var T17 = {
9963
9927
  r3xl: "var(--radius-3xl)",
9964
9928
  rFull: "var(--radius-full)"
9965
9929
  };
9966
- var GIVING_COLUMNS = "minmax(220px, 1fr) 120px 120px 150px 110px 150px";
9967
- var TRANSFER_COLUMNS = "120px 170px minmax(200px, 1fr) 120px";
9968
- var TRANSFERS_PAGE = 20;
9930
+ var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
9969
9931
  var cellText2 = {
9970
9932
  fontFamily: T17.font,
9971
9933
  fontSize: T17.fsSm,
@@ -9987,138 +9949,67 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ (0, import_jsx_runtime27.
9987
9949
  children: labels.map((label) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: label }, label))
9988
9950
  }
9989
9951
  );
9990
- function Badge({ text: text2, tone }) {
9991
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
9992
- "span",
9993
- {
9994
- className: "inline-flex items-center shrink-0",
9995
- style: {
9996
- ...cellText2,
9997
- fontSize: 12,
9998
- padding: "0 8px",
9999
- height: 22,
10000
- borderRadius: T17.rFull,
10001
- color: tone === "warning" ? T17.primary : T17.muted,
10002
- background: tone === "warning" ? T17.warningFill : T17.fillPlus,
10003
- border: tone === "warning" ? `1px solid ${T17.warningBorder}` : "1px solid transparent"
10004
- },
10005
- children: text2
10006
- }
10007
- );
10008
- }
10009
- function GivingRow({
10010
- allocation,
10011
- planNotRestored,
9952
+ function MemberRow2({
9953
+ member,
10012
9954
  onEdit,
10013
- onCancel
9955
+ onRemove
10014
9956
  }) {
10015
- const failed = initialTransferWarning(allocation);
10016
- const sent = initialTransferSummary(allocation);
10017
- const active = allocation.status === "active";
9957
+ const spent = member.remaining <= 0;
10018
9958
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10019
9959
  "div",
10020
9960
  {
10021
9961
  style: {
10022
9962
  borderBottom: `1px solid ${T17.borderSubtle}`,
10023
9963
  display: "grid",
10024
- gridTemplateColumns: GIVING_COLUMNS,
9964
+ gridTemplateColumns: MEMBER_COLUMNS2,
10025
9965
  alignItems: "center",
10026
9966
  padding: `10px ${T17.sp3xl}`,
10027
9967
  gap: 24
10028
9968
  },
10029
9969
  children: [
10030
9970
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10031
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: allocationWorkspaceName(allocation.targetWorkspaceName) }),
10032
- failed ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col", style: { gap: 2 }, children: [
10033
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { text: "First transfer failed", tone: "warning" }) }),
10034
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: failed })
10035
- ] }) : sent ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: sent }) : null,
10036
- allocation.note && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", title: allocation.note, style: { ...cellText2, color: T17.tertiary }, children: allocation.note })
10037
- ] }),
10038
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: formatAllocationSplit(allocation) }),
10039
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: formatAllocationRepeat(allocation.recurring) }),
10040
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-wrap items-center", style: { gap: 6 }, children: [
10041
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { text: active ? "Active" : "Canceled", tone: "neutral" }),
10042
- planNotRestored && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { text: "Plan not restored", tone: "warning" })
9971
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: poolWorkspaceName(member.memberWorkspaceName) }),
9972
+ member.note && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", title: member.note, style: { ...cellText2, color: T17.tertiary }, children: member.note })
10043
9973
  ] }),
10044
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(allocation.createdAt) }),
9974
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: describeAllowance(member) }),
9975
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: formatCredits(member.used) }),
9976
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: spent ? T17.warning : T17.primary }, children: formatCredits(member.remaining) }),
10045
9977
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center", style: { gap: 8 }, children: [
10046
- active && onEdit && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
10047
- active && onCancel && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Cancel", variant: "secondary", size: "sm", onClick: onCancel })
10048
- ] })
10049
- ]
10050
- }
10051
- );
10052
- }
10053
- function TransferRow({ transfer }) {
10054
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10055
- "div",
10056
- {
10057
- style: {
10058
- borderBottom: `1px solid ${T17.borderSubtle}`,
10059
- display: "grid",
10060
- gridTemplateColumns: TRANSFER_COLUMNS,
10061
- alignItems: "center",
10062
- padding: `8px ${T17.sp3xl}`,
10063
- gap: 24
10064
- },
10065
- children: [
10066
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(transfer.createdAt) }),
10067
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: cellText2, children: [
10068
- transfer.direction === "sent" ? "Sent" : "Received",
10069
- " \xB7",
10070
- " ",
10071
- transfer.kind === "initial" ? "Initial" : "Renewal"
10072
- ] }),
10073
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", style: cellText2, children: transfer.counterpartWorkspaceName ?? (transfer.counterpartWorkspaceId ? "Unknown workspace" : "\u2014") }),
10074
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, color: transfer.direction === "received" ? T17.success : T17.primary }, children: [
10075
- transfer.direction === "received" ? "+" : "\u2212",
10076
- formatCredits(transfer.credits)
9978
+ onEdit && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
9979
+ onRemove && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Remove", variant: "secondary", size: "sm", onClick: onRemove })
10077
9980
  ] })
10078
9981
  ]
10079
9982
  }
10080
9983
  );
10081
9984
  }
10082
- function CreditAllocationsView() {
9985
+ function SharedCreditsView() {
10083
9986
  const { api, workspace, toast } = useSettingsAdapters();
10084
9987
  const workspaceId = workspace.current?.id ?? null;
9988
+ const workspaceName = workspace.current?.name?.trim() || "this workspace";
10085
9989
  const [data, setData] = (0, import_react21.useState)(null);
10086
9990
  const [loading, setLoading] = (0, import_react21.useState)(true);
10087
9991
  const [failed, setFailed] = (0, import_react21.useState)(false);
10088
- const [targets, setTargets] = (0, import_react21.useState)([]);
10089
- const [transfers, setTransfers] = (0, import_react21.useState)([]);
10090
- const [transfersTotal, setTransfersTotal] = (0, import_react21.useState)(0);
10091
- const [transfersPage, setTransfersPage] = (0, import_react21.useState)(0);
10092
- const [transfersFailed, setTransfersFailed] = (0, import_react21.useState)(false);
10093
- const [loadingMore, setLoadingMore] = (0, import_react21.useState)(false);
10094
- const [creating, setCreating] = (0, import_react21.useState)(false);
9992
+ const [candidates, setCandidates] = (0, import_react21.useState)([]);
9993
+ const [adding, setAdding] = (0, import_react21.useState)(false);
10095
9994
  const [editing, setEditing] = (0, import_react21.useState)(null);
10096
- const [canceling, setCanceling] = (0, import_react21.useState)(null);
10097
- const [planNotRestored, setPlanNotRestored] = (0, import_react21.useState)(() => /* @__PURE__ */ new Set());
9995
+ const [removing, setRemoving] = (0, import_react21.useState)(null);
10098
9996
  const latestLoad = (0, import_react21.useRef)(0);
10099
9997
  const load = (0, import_react21.useCallback)(
10100
9998
  async ({ refresh = false } = {}) => {
10101
9999
  const loadId = ++latestLoad.current;
10102
- if (!api.fetchCreditAllocations) {
10000
+ if (!api.fetchCreditPool) {
10103
10001
  setLoading(false);
10104
10002
  setFailed(true);
10105
10003
  return;
10106
10004
  }
10107
- const [next, page] = await Promise.all([
10108
- api.fetchCreditAllocations().catch(() => null),
10109
- api.fetchCreditTransfers ? api.fetchCreditTransfers({ page: 1, limit: TRANSFERS_PAGE }).catch(() => null) : Promise.resolve(null)
10110
- ]);
10005
+ const next = await api.fetchCreditPool().catch(() => null);
10111
10006
  if (loadId !== latestLoad.current) return;
10112
10007
  if (refresh && !next) {
10113
- toast.error("Couldn't refresh credit allocations. Reopen this page to see the latest.");
10008
+ toast.error("Couldn't refresh shared credits. Reopen this page to see the latest.");
10114
10009
  return;
10115
10010
  }
10116
10011
  setFailed(!next);
10117
10012
  setData(next);
10118
- setTransfersFailed(Boolean(api.fetchCreditTransfers) && !page);
10119
- setTransfers(page?.transfers ?? []);
10120
- setTransfersTotal(page?.total ?? 0);
10121
- setTransfersPage(page ? 1 : 0);
10122
10013
  setLoading(false);
10123
10014
  },
10124
10015
  [api, toast]
@@ -10127,11 +10018,9 @@ function CreditAllocationsView() {
10127
10018
  setLoading(true);
10128
10019
  setFailed(false);
10129
10020
  setData(null);
10130
- setTransfers([]);
10131
- setCreating(false);
10021
+ setAdding(false);
10132
10022
  setEditing(null);
10133
- setCanceling(null);
10134
- setPlanNotRestored(/* @__PURE__ */ new Set());
10023
+ setRemoving(null);
10135
10024
  void load();
10136
10025
  }, [workspaceId]);
10137
10026
  (0, import_react21.useEffect)(
@@ -10141,36 +10030,21 @@ function CreditAllocationsView() {
10141
10030
  []
10142
10031
  );
10143
10032
  const canManage = Boolean(data?.canManage);
10144
- const canCreate = canManage && Boolean(api.createCreditAllocation);
10145
- const openCreate = async () => {
10146
- if (!api.fetchAllocationTargets) {
10147
- setTargets([]);
10148
- setCreating(true);
10033
+ const sponsor = data?.sponsor ?? null;
10034
+ const canAdd = canManage && !sponsor && Boolean(api.addCreditPoolMember);
10035
+ const openAdd = async () => {
10036
+ if (!api.fetchCreditPoolCandidates) {
10037
+ setCandidates([]);
10038
+ setAdding(true);
10149
10039
  return;
10150
10040
  }
10151
10041
  try {
10152
- setTargets(await api.fetchAllocationTargets());
10153
- setCreating(true);
10042
+ setCandidates(await api.fetchCreditPoolCandidates());
10043
+ setAdding(true);
10154
10044
  } catch {
10155
10045
  toast.error("Couldn't load this organization's workspaces. Try again.");
10156
10046
  }
10157
10047
  };
10158
- const loadMoreTransfers = async () => {
10159
- if (!api.fetchCreditTransfers || loadingMore) return;
10160
- const loadId = latestLoad.current;
10161
- setLoadingMore(true);
10162
- try {
10163
- const page = await api.fetchCreditTransfers({ page: transfersPage + 1, limit: TRANSFERS_PAGE });
10164
- if (loadId !== latestLoad.current) return;
10165
- setTransfers((prev) => [...prev, ...page.transfers]);
10166
- setTransfersTotal(page.total);
10167
- setTransfersPage(transfersPage + 1);
10168
- } catch {
10169
- toast.error("Couldn't load more transfers. Try again.");
10170
- } finally {
10171
- setLoadingMore(false);
10172
- }
10173
- };
10174
10048
  if (loading) {
10175
10049
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10176
10050
  "div",
@@ -10193,9 +10067,7 @@ function CreditAllocationsView() {
10193
10067
  }
10194
10068
  );
10195
10069
  }
10196
- const giving = data?.giving ?? [];
10197
- const sponsor = data?.receiving.find((a) => a.status === "active") ?? null;
10198
- const balance = typeof workspace.current?.availableCredits === "number" ? workspace.current.availableCredits : null;
10070
+ const members = data?.members ?? [];
10199
10071
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col flex-1 min-h-0 w-full overflow-y-auto", style: { gap: 16 }, children: [
10200
10072
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start justify-between shrink-0 w-full", style: { gap: 12 }, children: [
10201
10073
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 4 }, children: [
@@ -10203,12 +10075,12 @@ function CreditAllocationsView() {
10203
10075
  "span",
10204
10076
  {
10205
10077
  style: { fontFamily: T17.font, fontSize: T17.fsXl, fontWeight: 500, lineHeight: T17.lhXl, color: T17.primary },
10206
- children: "Credit allocations"
10078
+ children: "Shared credits"
10207
10079
  }
10208
10080
  ),
10209
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: canManage ? "Send this workspace's credits to other workspaces in your organization, once or on every renewal." : "Credits this workspace shares with other workspaces in its organization. Only organization admins can change them." })
10081
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: canManage ? "Let other workspaces in your organization spend this workspace's credits, up to a share each billing cycle. The credits stay here." : "Workspaces in this organization that spend this workspace's credits. Only organization admins can change them." })
10210
10082
  ] }),
10211
- canCreate && !failed && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "New allocation", variant: "primary", size: "md", onClick: () => void openCreate() })
10083
+ canAdd && !failed && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Share credits", variant: "primary", size: "md", onClick: () => void openAdd() })
10212
10084
  ] }),
10213
10085
  sponsor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10214
10086
  "div",
@@ -10222,103 +10094,74 @@ function CreditAllocationsView() {
10222
10094
  borderRadius: T17.r3xl
10223
10095
  },
10224
10096
  children: [
10225
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, fontWeight: 500 }, children: [
10226
- "Funded by ",
10227
- allocationWorkspaceName(sponsor.sourceWorkspaceName)
10228
- ] }),
10097
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: sharedPoolBannerText({
10098
+ sponsorName: poolWorkspaceName(sponsor.sponsorWorkspaceName),
10099
+ allowance: sponsor.allowance,
10100
+ remaining: sponsor.remaining
10101
+ }) }),
10229
10102
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, color: T17.muted }, children: [
10230
- "Receives ",
10231
- describeAllocation(sponsor),
10103
+ "This workspace spends ",
10104
+ poolWorkspaceName(sponsor.sponsorWorkspaceName),
10105
+ "'s credits, not its own",
10106
+ sponsor.cycle?.end ? `, until its allowance starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10232
10107
  "."
10233
10108
  ] })
10234
10109
  ]
10235
10110
  }
10236
10111
  ),
10237
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10238
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: "Giving" }),
10112
+ !sponsor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10113
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: "Shared with" }),
10239
10114
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10240
10115
  MemberListFrame,
10241
10116
  {
10242
10117
  grow: "cap",
10243
10118
  maxHeight: 420,
10244
- contentMinWidth: 940,
10245
- header: tableHeader(GIVING_COLUMNS, ["Workspace", "Split", "Repeats", "Status", "Created", "Actions"]),
10246
- isEmpty: failed || giving.length === 0,
10247
- emptyText: failed ? "Couldn't load credit allocations. Try again later." : "This workspace doesn't send credits to any other workspace.",
10248
- children: giving.map((allocation) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10249
- GivingRow,
10119
+ contentMinWidth: 860,
10120
+ header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Actions"]),
10121
+ isEmpty: failed || members.length === 0,
10122
+ emptyText: failed ? "Couldn't load shared credits. Try again later." : "This workspace doesn't share its credits with any other workspace.",
10123
+ children: members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10124
+ MemberRow2,
10250
10125
  {
10251
- allocation,
10252
- planNotRestored: planNotRestored.has(allocation._id),
10253
- onEdit: canManage && api.updateCreditAllocation ? () => setEditing(allocation) : void 0,
10254
- onCancel: canManage && api.cancelCreditAllocation ? () => setCanceling(allocation) : void 0
10126
+ member,
10127
+ onEdit: canManage && api.updateCreditPoolMember ? () => setEditing(member) : void 0,
10128
+ onRemove: canManage && api.removeCreditPoolMember ? () => setRemoving(member) : void 0
10255
10129
  },
10256
- allocation._id
10130
+ member._id
10257
10131
  ))
10258
10132
  }
10259
10133
  )
10260
10134
  ] }),
10261
- api.fetchCreditTransfers && !failed && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10262
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: "Transfer history" }),
10263
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10264
- MemberListFrame,
10265
- {
10266
- grow: "cap",
10267
- maxHeight: 420,
10268
- contentMinWidth: 680,
10269
- header: tableHeader(TRANSFER_COLUMNS, ["Date", "Type", "Workspace", "Credits"]),
10270
- isEmpty: transfersFailed || transfers.length === 0,
10271
- emptyText: transfersFailed ? "Couldn't load transfers. Try again later." : "No transfers yet",
10272
- children: transfers.map((transfer) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TransferRow, { transfer }, transfer.id))
10273
- }
10274
- ),
10275
- transfers.length < transfersTotal && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10276
- import_ui15.ScButton,
10277
- {
10278
- text: loadingMore ? "Loading\u2026" : "Load more",
10279
- variant: "secondary",
10280
- size: "sm",
10281
- state: loadingMore ? "disabled" : "default",
10282
- onClick: loadingMore ? void 0 : () => void loadMoreTransfers()
10283
- }
10284
- ) })
10285
- ] }),
10286
- creating && api.createCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10287
- CreditAllocationModal,
10135
+ adding && api.addCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10136
+ CreditPoolMemberModal,
10288
10137
  {
10289
- kind: "create",
10290
- targets,
10291
- balance,
10292
- onSave: (input) => api.createCreditAllocation(input),
10293
- onClose: () => setCreating(false),
10138
+ kind: "add",
10139
+ candidates,
10140
+ onSave: (input) => api.addCreditPoolMember(input),
10141
+ onClose: () => setAdding(false),
10294
10142
  onSaved: () => void load({ refresh: true })
10295
10143
  }
10296
10144
  ),
10297
- editing && api.updateCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10298
- CreditAllocationModal,
10145
+ editing && api.updateCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10146
+ CreditPoolMemberModal,
10299
10147
  {
10300
10148
  kind: "edit",
10301
- allocation: editing,
10302
- targetName: allocationWorkspaceName(editing.targetWorkspaceName),
10303
- onSave: (patch) => api.updateCreditAllocation(editing._id, patch),
10149
+ member: editing,
10150
+ memberName: poolWorkspaceName(editing.memberWorkspaceName),
10151
+ onSave: (patch) => api.updateCreditPoolMember(editing._id, patch),
10304
10152
  onClose: () => setEditing(null),
10305
10153
  onSaved: () => void load({ refresh: true })
10306
10154
  }
10307
10155
  ),
10308
- canceling && api.cancelCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10309
- CancelCreditAllocationModal,
10156
+ removing && api.removeCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10157
+ RemovePoolMemberModal,
10310
10158
  {
10311
- allocation: canceling,
10312
- targetName: allocationWorkspaceName(canceling.targetWorkspaceName),
10313
- onCancel: (reason) => api.cancelCreditAllocation(canceling._id, reason),
10314
- onClose: () => setCanceling(null),
10315
- onCanceled: ({ planNotRestored: stuck }) => {
10316
- if (stuck) {
10317
- const id = canceling._id;
10318
- setPlanNotRestored((prev) => new Set(prev).add(id));
10319
- }
10320
- void load({ refresh: true });
10321
- }
10159
+ member: removing,
10160
+ memberName: poolWorkspaceName(removing.memberWorkspaceName),
10161
+ sponsorName: workspaceName,
10162
+ onRemove: (reason) => api.removeCreditPoolMember(removing._id, reason),
10163
+ onClose: () => setRemoving(null),
10164
+ onRemoved: () => void load({ refresh: true })
10322
10165
  }
10323
10166
  )
10324
10167
  ] });
@@ -11208,8 +11051,11 @@ function InvoiceHistoryTable({
11208
11051
  function UsageLogsTable({
11209
11052
  logs,
11210
11053
  userMap,
11211
- loading
11054
+ loading,
11055
+ currentWorkspaceId = null,
11056
+ usedByNames = {}
11212
11057
  }) {
11058
+ const showWorkspace = logs.some((log) => usedByWorkspaceKey(log, currentWorkspaceId));
11213
11059
  if (loading) {
11214
11060
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11215
11061
  "div",
@@ -11286,6 +11132,14 @@ function UsageLogsTable({
11286
11132
  style: { flexShrink: 0, width: "7.5rem" }
11287
11133
  }
11288
11134
  ),
11135
+ showWorkspace && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11136
+ import_ui16.ScHeader,
11137
+ {
11138
+ text: "Workspace",
11139
+ state: "none",
11140
+ style: { flexShrink: 0, width: "9rem" }
11141
+ }
11142
+ ),
11289
11143
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11290
11144
  import_ui16.ScHeader,
11291
11145
  {
@@ -11332,6 +11186,8 @@ function UsageLogsTable({
11332
11186
  logs.map((log) => {
11333
11187
  const userName = log.triggeredByUserId ? userMap[log.triggeredByUserId] || "Unknown" : "System";
11334
11188
  const app = log.service?.serviceGroupName || "\u2014";
11189
+ const usedBy = usedByWorkspaceKey(log, currentWorkspaceId);
11190
+ const workspaceLabel2 = usedBy ? `Used by ${usedByNames[usedBy.toLowerCase()] ?? "another workspace"}` : "This workspace";
11335
11191
  const activity = log.type === "return" ? "Returned Credits" : log.meta?.info || "Credit usage";
11336
11192
  const isPositive = log.type === "return" || log.type === "add";
11337
11193
  const signedAmount = `${isPositive ? "+" : "-"}${log.amount}`;
@@ -11361,6 +11217,18 @@ function UsageLogsTable({
11361
11217
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, flex: 1 }, children: app }),
11362
11218
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, flex: 1 }, children: activity }),
11363
11219
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: userName }),
11220
+ showWorkspace && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11221
+ "div",
11222
+ {
11223
+ style: {
11224
+ ...rowCellRegular,
11225
+ width: "9rem",
11226
+ color: usedBy ? "var(--alias-text---icons-primary, #f5f5f5)" : "var(--alias-text---icons-muted)"
11227
+ },
11228
+ title: workspaceLabel2,
11229
+ children: workspaceLabel2
11230
+ }
11231
+ ),
11364
11232
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: formatDate3(log.createdAt) }),
11365
11233
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11366
11234
  "div",
@@ -11429,8 +11297,8 @@ function BillingContent({
11429
11297
  onShowPlansChange,
11430
11298
  showPerUserLimits: showPerUserLimitsProp,
11431
11299
  onShowPerUserLimitsChange,
11432
- showCreditAllocations: showCreditAllocationsProp,
11433
- onShowCreditAllocationsChange
11300
+ showSharedCredits: showSharedCreditsProp,
11301
+ onShowSharedCreditsChange
11434
11302
  } = {}) {
11435
11303
  const { auth, workspace, organization, api, toast, navigation } = useSettingsAdapters();
11436
11304
  const user = auth.user;
@@ -11455,21 +11323,23 @@ function BillingContent({
11455
11323
  if (onShowPerUserLimitsChange) onShowPerUserLimitsChange(next);
11456
11324
  else setShowPerUserLimitsInternal(next);
11457
11325
  };
11458
- const canAllocateCredits = canShowCreditAllocations(
11326
+ const canShareCredits = canShowSharedCredits(
11459
11327
  api,
11460
11328
  workspace.current,
11461
11329
  organization?.current
11462
11330
  );
11463
- const [showCreditAllocationsInternal, setShowCreditAllocationsInternal] = (0, import_react22.useState)(false);
11464
- const showCreditAllocations = (showCreditAllocationsProp ?? showCreditAllocationsInternal) && canAllocateCredits;
11465
- const setShowCreditAllocations = (next) => {
11466
- if (onShowCreditAllocationsChange) onShowCreditAllocationsChange(next);
11467
- else setShowCreditAllocationsInternal(next);
11331
+ const [showSharedCreditsInternal, setShowSharedCreditsInternal] = (0, import_react22.useState)(false);
11332
+ const showSharedCredits = (showSharedCreditsProp ?? showSharedCreditsInternal) && canShareCredits;
11333
+ const setShowSharedCredits = (next) => {
11334
+ if (onShowSharedCreditsChange) onShowSharedCreditsChange(next);
11335
+ else setShowSharedCreditsInternal(next);
11468
11336
  };
11469
11337
  const [showExportModal, setShowExportModal] = (0, import_react22.useState)(false);
11470
11338
  const [showBuyCreditsModal, setShowBuyCreditsModal] = (0, import_react22.useState)(false);
11471
11339
  const [subscription, setSubscription] = (0, import_react22.useState)(null);
11472
11340
  const [availableCredits, setAvailableCredits] = (0, import_react22.useState)(null);
11341
+ const [sharedPool, setSharedPool] = (0, import_react22.useState)(null);
11342
+ const [usedByNames, setUsedByNames] = (0, import_react22.useState)({});
11473
11343
  const [products, setProducts] = (0, import_react22.useState)([]);
11474
11344
  const [invoices, setInvoices] = (0, import_react22.useState)([]);
11475
11345
  const [totalCredits, setTotalCredits] = (0, import_react22.useState)(null);
@@ -11520,6 +11390,7 @@ function BillingContent({
11520
11390
  if (subRes?.data) setSubscription(subRes.data);
11521
11391
  if (creditsRes?.availableCredits != null)
11522
11392
  setAvailableCredits(creditsRes.availableCredits);
11393
+ setSharedPool(creditsRes?.sharedPool ?? null);
11523
11394
  if (productsRes?.data) {
11524
11395
  const normalizedWorkspaceCountry = workspace.current?.country?.trim().toUpperCase();
11525
11396
  const targetCurrency = normalizedWorkspaceCountry === "IN" ? "inr" : "usd";
@@ -11568,7 +11439,12 @@ function BillingContent({
11568
11439
  );
11569
11440
  } else if (api.fetchUsageLogs) {
11570
11441
  const res = await api.fetchUsageLogs();
11571
- setUsageLogs(res.data?.length ? res.data : []);
11442
+ const logs = res.data?.length ? res.data : [];
11443
+ setUsageLogs(logs);
11444
+ if (api.fetchCreditPool && logs.some((log) => usedByWorkspaceKey(log, workspaceId))) {
11445
+ const pool = await api.fetchCreditPool().catch(() => null);
11446
+ setUsedByNames(usedByNamesFrom(pool));
11447
+ }
11572
11448
  } else {
11573
11449
  setUsageLogs([]);
11574
11450
  }
@@ -11579,7 +11455,7 @@ function BillingContent({
11579
11455
  setLoadingTable(false);
11580
11456
  }
11581
11457
  },
11582
- [api]
11458
+ [api, workspaceId]
11583
11459
  );
11584
11460
  const handlePlanAction = (0, import_react22.useCallback)(
11585
11461
  async ({ productId, priceId, planName: planName2 }) => {
@@ -11737,10 +11613,13 @@ function BillingContent({
11737
11613
  );
11738
11614
  const scheduledDowngradeText = subscription?.downgradeScheduled && subscription.plan?.name ? `Downgrades to ${subscription.plan.name}${scheduledDowngradeDateLabel ? ` ${scheduledDowngradeDateLabel}` : ""}` : void 0;
11739
11615
  const isPlanDetailsLoading = !billingOverviewLoaded || loadingBilling || subscription?.plan == null || !planName || planName === "-" || !planAmount || currentPlanCredits == null;
11740
- const usedCredits = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
11741
- const progressPct = usedCredits != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(usedCredits / totalCredits * 100)) : 0;
11742
- const remainingPct = 100 - progressPct;
11743
- const usageText = usedCredits != null ? formatCredits2(usedCredits) : availableCredits != null ? `${formatCredits2(availableCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11616
+ const {
11617
+ used: usedCredits,
11618
+ remaining: remainingCredits,
11619
+ progressPct,
11620
+ remainingPct
11621
+ } = creditsMeter({ availableCredits, totalCredits, sharedPool });
11622
+ const usageText = usedCredits != null ? formatCredits2(usedCredits) : remainingCredits != null ? `${formatCredits2(remainingCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11744
11623
  const handleExport = async (startDate, endDate) => {
11745
11624
  const end = new Date(endDate);
11746
11625
  end.setHours(23, 59, 59, 999);
@@ -11817,7 +11696,7 @@ function BillingContent({
11817
11696
  function fmtFilename(d) {
11818
11697
  return d.toISOString().slice(0, 10);
11819
11698
  }
11820
- async function handleBuyCredits(credits, price) {
11699
+ async function handleBuyCredits(credits2, price) {
11821
11700
  if (!workspaceId) {
11822
11701
  toast.error("Missing workspace details for credit top-up");
11823
11702
  return;
@@ -11829,14 +11708,14 @@ function BillingContent({
11829
11708
  "Buy credits confirmed",
11830
11709
  {
11831
11710
  action: "confirm_buy_credits",
11832
- additionalInfo: `${credits}:${price}`
11711
+ additionalInfo: `${credits2}:${price}`
11833
11712
  }
11834
11713
  );
11835
11714
  try {
11836
11715
  const topupCurrency = workspace.current?.country?.trim().toUpperCase() === "IN" ? "inr" : "usd";
11837
11716
  const res = await api.buyCredits({
11838
11717
  serviceName: DEFAULT_TOPUP_SERVICE,
11839
- credits,
11718
+ credits: credits2,
11840
11719
  unitAmount: price,
11841
11720
  itemCount: 1,
11842
11721
  currency: topupCurrency,
@@ -11854,7 +11733,7 @@ function BillingContent({
11854
11733
  setAvailableCredits(payloadData.availableCredits);
11855
11734
  } else {
11856
11735
  setAvailableCredits(
11857
- (prev) => typeof prev === "number" ? prev + credits : prev
11736
+ (prev) => typeof prev === "number" ? prev + credits2 : prev
11858
11737
  );
11859
11738
  }
11860
11739
  toast.success("Top-up created");
@@ -11924,13 +11803,13 @@ function BillingContent({
11924
11803
  }
11925
11804
  ) });
11926
11805
  }
11927
- if (showCreditAllocations) {
11928
- const allocationsView = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col flex-1 h-full min-h-0 min-w-0 w-full", style: { gap: 16 }, children: [
11929
- !onShowCreditAllocationsChange && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(BackToBilling, { onClick: () => setShowCreditAllocations(false) }),
11930
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(CreditAllocationsView, {})
11806
+ if (showSharedCredits) {
11807
+ const sharedCreditsView = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col flex-1 h-full min-h-0 min-w-0 w-full", style: { gap: 16 }, children: [
11808
+ !onShowSharedCreditsChange && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(BackToBilling, { onClick: () => setShowSharedCredits(false) }),
11809
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SharedCreditsView, {})
11931
11810
  ] });
11932
- if (embedded) return allocationsView;
11933
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: allocationsView });
11811
+ if (embedded) return sharedCreditsView;
11812
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: sharedCreditsView });
11934
11813
  }
11935
11814
  if (showPerUserLimits) {
11936
11815
  const limitsView = /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col flex-1 h-full min-h-0 min-w-0 w-full", style: { gap: 16 }, children: [
@@ -11997,7 +11876,7 @@ function BillingContent({
11997
11876
  {
11998
11877
  usageLabel: "Credits used",
11999
11878
  usageText,
12000
- remainingText: availableCredits != null ? `${formatCredits2(availableCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
11879
+ remainingText: remainingCredits != null ? `${formatCredits2(remainingCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
12001
11880
  buyButtonText: "Buy credits",
12002
11881
  buyButtonState: isFreePlan ? "disabled" : "default",
12003
11882
  buyButtonHoverText: isFreePlan ? "Pro plan required" : void 0,
@@ -12017,6 +11896,50 @@ function BillingContent({
12017
11896
  ]
12018
11897
  }
12019
11898
  ),
11899
+ sharedPool ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
11900
+ "div",
11901
+ {
11902
+ role: "status",
11903
+ className: "flex flex-col shrink-0 w-full",
11904
+ style: {
11905
+ gap: 4,
11906
+ padding: 16,
11907
+ backgroundColor: "var(--alias-surface-base)",
11908
+ border: "1px solid var(--alias-border-subtle)",
11909
+ borderRadius: "var(--radius-3xl)"
11910
+ },
11911
+ children: [
11912
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11913
+ "span",
11914
+ {
11915
+ style: {
11916
+ fontFamily: "var(--font-family-font-family-body)",
11917
+ fontSize: "var(--font-size-font-size-sm)",
11918
+ fontWeight: 500,
11919
+ lineHeight: "var(--line-height-line-height-sm)",
11920
+ color: "var(--alias-text---icons-primary)"
11921
+ },
11922
+ children: sharedPoolBannerText(sharedPool)
11923
+ }
11924
+ ),
11925
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
11926
+ "span",
11927
+ {
11928
+ style: {
11929
+ fontFamily: "var(--font-family-font-family-body)",
11930
+ fontSize: "var(--font-size-font-size-sm)",
11931
+ lineHeight: "var(--line-height-line-height-sm)",
11932
+ color: "var(--alias-text---icons-muted)"
11933
+ },
11934
+ children: [
11935
+ "This workspace spends these shared credits, not its own; credits bought here can't be used while it does.",
11936
+ sharedPool.cycleEnd ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : ""
11937
+ ]
11938
+ }
11939
+ )
11940
+ ]
11941
+ }
11942
+ ) : null,
12020
11943
  usage ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12021
11944
  "div",
12022
11945
  {
@@ -12090,7 +12013,7 @@ function BillingContent({
12090
12013
  ]
12091
12014
  }
12092
12015
  ) : null,
12093
- canAllocateCredits ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12016
+ canShareCredits ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12094
12017
  "div",
12095
12018
  {
12096
12019
  className: "flex items-center justify-between shrink-0 w-full",
@@ -12113,7 +12036,7 @@ function BillingContent({
12113
12036
  lineHeight: "var(--line-height-line-height-sm)",
12114
12037
  color: "var(--alias-text---icons-primary)"
12115
12038
  },
12116
- children: "Credit allocations"
12039
+ children: "Shared credits"
12117
12040
  }
12118
12041
  ),
12119
12042
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12125,7 +12048,7 @@ function BillingContent({
12125
12048
  lineHeight: "var(--line-height-line-height-sm)",
12126
12049
  color: "var(--alias-text---icons-muted)"
12127
12050
  },
12128
- children: "Share this workspace's credits with other workspaces in your organization"
12051
+ children: "Let other workspaces in your organization spend this workspace's credits"
12129
12052
  }
12130
12053
  )
12131
12054
  ] }),
@@ -12135,7 +12058,7 @@ function BillingContent({
12135
12058
  text: "Manage",
12136
12059
  variant: "secondary",
12137
12060
  size: "md",
12138
- onClick: () => setShowCreditAllocations(true)
12061
+ onClick: () => setShowSharedCredits(true)
12139
12062
  }
12140
12063
  )
12141
12064
  ]
@@ -12185,7 +12108,9 @@ function BillingContent({
12185
12108
  {
12186
12109
  logs: usageLogs,
12187
12110
  userMap,
12188
- loading: loadingTable
12111
+ loading: loadingTable,
12112
+ currentWorkspaceId: workspaceId,
12113
+ usedByNames
12189
12114
  }
12190
12115
  ),
12191
12116
  showExportModal && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12204,8 +12129,8 @@ function BillingContent({
12204
12129
  workspace.current?.country
12205
12130
  ),
12206
12131
  onClose: () => setShowBuyCreditsModal(false),
12207
- onBuy: (credits, price) => {
12208
- handleBuyCredits(credits, price);
12132
+ onBuy: (credits2, price) => {
12133
+ handleBuyCredits(credits2, price);
12209
12134
  }
12210
12135
  }
12211
12136
  )
@@ -14853,20 +14778,20 @@ function SettingsContentInner({
14853
14778
  const isBillingPlans = activeTab === "billing" && isWorkspaceAdmin && billingShowPlans;
14854
14779
  const [billingShowPerUserLimits, setBillingShowPerUserLimits] = (0, import_react26.useState)(false);
14855
14780
  const isBillingPerUserLimits = activeTab === "billing" && isWorkspaceAdmin && billingShowPerUserLimits && !billingShowPlans;
14856
- const [billingShowCreditAllocations, setBillingShowCreditAllocations] = (0, import_react26.useState)(false);
14857
- const isBillingCreditAllocations = activeTab === "billing" && isWorkspaceAdmin && billingShowCreditAllocations && !billingShowPlans && !billingShowPerUserLimits;
14858
- const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingCreditAllocations;
14781
+ const [billingShowSharedCredits, setBillingShowSharedCredits] = (0, import_react26.useState)(false);
14782
+ const isBillingSharedCredits = activeTab === "billing" && isWorkspaceAdmin && billingShowSharedCredits && !billingShowPlans && !billingShowPerUserLimits;
14783
+ const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingSharedCredits;
14859
14784
  (0, import_react26.useEffect)(() => {
14860
14785
  if (activeTab !== "billing" || !isWorkspaceAdmin) {
14861
14786
  setBillingShowPlans(false);
14862
14787
  setBillingShowPerUserLimits(false);
14863
- setBillingShowCreditAllocations(false);
14788
+ setBillingShowSharedCredits(false);
14864
14789
  }
14865
14790
  }, [activeTab, isWorkspaceAdmin]);
14866
14791
  (0, import_react26.useEffect)(() => {
14867
14792
  setBillingShowPlans(false);
14868
14793
  setBillingShowPerUserLimits(false);
14869
- setBillingShowCreditAllocations(false);
14794
+ setBillingShowSharedCredits(false);
14870
14795
  }, [workspaceId]);
14871
14796
  (0, import_react26.useEffect)(() => {
14872
14797
  if (!onClose) return;
@@ -14881,15 +14806,15 @@ function SettingsContentInner({
14881
14806
  setBillingShowPerUserLimits(false);
14882
14807
  return;
14883
14808
  }
14884
- if (billingShowCreditAllocations) {
14885
- setBillingShowCreditAllocations(false);
14809
+ if (billingShowSharedCredits) {
14810
+ setBillingShowSharedCredits(false);
14886
14811
  return;
14887
14812
  }
14888
14813
  onClose();
14889
14814
  };
14890
14815
  document.addEventListener("keydown", handleKeyDown);
14891
14816
  return () => document.removeEventListener("keydown", handleKeyDown);
14892
- }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowCreditAllocations]);
14817
+ }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowSharedCredits]);
14893
14818
  const [localWorkspaces, setLocalWorkspaces] = (0, import_react26.useState)(null);
14894
14819
  const [wsModalOpen, setWsModalOpen] = (0, import_react26.useState)(false);
14895
14820
  const [wsModalMode, setWsModalMode] = (0, import_react26.useState)("create");
@@ -15210,7 +15135,7 @@ function SettingsContentInner({
15210
15135
  if (isBillingSubView) {
15211
15136
  setBillingShowPlans(false);
15212
15137
  setBillingShowPerUserLimits(false);
15213
- setBillingShowCreditAllocations(false);
15138
+ setBillingShowSharedCredits(false);
15214
15139
  return;
15215
15140
  }
15216
15141
  if (onBack) onBack();
@@ -15313,7 +15238,7 @@ function SettingsContentInner({
15313
15238
  }
15314
15239
  )
15315
15240
  ] }),
15316
- isBillingCreditAllocations && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
15241
+ isBillingSharedCredits && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
15317
15242
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_icons17.SiconRight, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15318
15243
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
15319
15244
  "span",
@@ -15325,7 +15250,7 @@ function SettingsContentInner({
15325
15250
  lineHeight: "var(--line-height-line-height-xl)",
15326
15251
  color: "var(--alias-text---icons-primary)"
15327
15252
  },
15328
- children: "Credit allocations"
15253
+ children: "Shared credits"
15329
15254
  }
15330
15255
  )
15331
15256
  ] })
@@ -15538,7 +15463,7 @@ function SettingsContentInner({
15538
15463
  setBillingShowPlans(open);
15539
15464
  if (open) {
15540
15465
  setBillingShowPerUserLimits(false);
15541
- setBillingShowCreditAllocations(false);
15466
+ setBillingShowSharedCredits(false);
15542
15467
  }
15543
15468
  },
15544
15469
  showPerUserLimits: billingShowPerUserLimits,
@@ -15546,12 +15471,12 @@ function SettingsContentInner({
15546
15471
  setBillingShowPerUserLimits(open);
15547
15472
  if (open) {
15548
15473
  setBillingShowPlans(false);
15549
- setBillingShowCreditAllocations(false);
15474
+ setBillingShowSharedCredits(false);
15550
15475
  }
15551
15476
  },
15552
- showCreditAllocations: billingShowCreditAllocations,
15553
- onShowCreditAllocationsChange: (open) => {
15554
- setBillingShowCreditAllocations(open);
15477
+ showSharedCredits: billingShowSharedCredits,
15478
+ onShowSharedCreditsChange: (open) => {
15479
+ setBillingShowSharedCredits(open);
15555
15480
  if (open) {
15556
15481
  setBillingShowPlans(false);
15557
15482
  setBillingShowPerUserLimits(false);