@streamoid/settings 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,21 @@ __export(index_exports, {
98
92
  loadWorkspaceOrganization: () => loadWorkspaceOrganization,
99
93
  memberRoleUpdate: () => memberRoleUpdate,
100
94
  minimumCreditLimit: () => minimumCreditLimit,
95
+ pausedOwnCreditsText: () => pausedOwnCreditsText,
96
+ poolErrorMessage: () => poolErrorMessage,
97
+ poolWorkspaceName: () => poolWorkspaceName,
101
98
  resolveSettingsTabFromPath: () => resolveSettingsTabFromPath,
102
99
  roundCredits: () => roundCredits,
100
+ sharedPoolBannerText: () => sharedPoolBannerText,
103
101
  useSettingsAdapters: () => useSettingsAdapters,
104
102
  useWorkspaceOrganization: () => useWorkspaceOrganization,
103
+ usedByNamesFrom: () => usedByNamesFrom,
104
+ usedByWorkspaceKey: () => usedByWorkspaceKey,
105
105
  userLanguageLabel: () => userLanguageLabel,
106
- validateAllocationText: () => validateAllocationText,
107
- validateAllocationValue: () => validateAllocationValue,
108
106
  validateCreditLimitInput: () => validateCreditLimitInput,
109
- validateDefaultCreditLimitInput: () => validateDefaultCreditLimitInput
107
+ validateDefaultCreditLimitInput: () => validateDefaultCreditLimitInput,
108
+ validatePoolText: () => validatePoolText,
109
+ validatePoolValue: () => validatePoolValue
110
110
  });
111
111
  module.exports = __toCommonJS(index_exports);
112
112
 
@@ -8032,14 +8032,14 @@ function BuyCreditsModal({
8032
8032
  const maxCredits = Math.round(
8033
8033
  topupConfig.maxAmount / topupConfig.pricePerCredit
8034
8034
  );
8035
- const [credits, setCredits] = (0, import_react15.useState)(minCredits);
8035
+ const [credits2, setCredits] = (0, import_react15.useState)(minCredits);
8036
8036
  const [creditsInput, setCreditsInput] = (0, import_react15.useState)(String(minCredits));
8037
8037
  const [priceInput, setPriceInput] = (0, import_react15.useState)(
8038
8038
  String(minCredits * topupConfig.pricePerCredit)
8039
8039
  );
8040
8040
  const [activeField, setActiveField] = (0, import_react15.useState)("credits");
8041
- const parsedCredits = creditsInput.length ? Number(creditsInput) : credits;
8042
- const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits;
8041
+ const parsedCredits = creditsInput.length ? Number(creditsInput) : credits2;
8042
+ const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits2;
8043
8043
  const pricedCredits = previewCredits > 0 ? previewCredits : 0;
8044
8044
  const price = Number(
8045
8045
  (pricedCredits * topupConfig.pricePerCredit).toFixed(2)
@@ -8061,8 +8061,8 @@ function BuyCreditsModal({
8061
8061
  setCreditsInput(String(derived));
8062
8062
  setPriceInput(String(amount));
8063
8063
  };
8064
- const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits - STEP));
8065
- const handleIncrement = () => syncFromCredits(Math.min(maxCredits, credits + STEP));
8064
+ const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits2 - STEP));
8065
+ const handleIncrement = () => syncFromCredits(Math.min(maxCredits, credits2 + STEP));
8066
8066
  const handleCreditsChange = (value) => {
8067
8067
  const raw = value.replace(/[^\d]/g, "");
8068
8068
  setCreditsInput(raw);
@@ -8196,7 +8196,7 @@ function BuyCreditsModal({
8196
8196
  style: {
8197
8197
  width: 24,
8198
8198
  height: 24,
8199
- opacity: credits <= minCredits ? 0.3 : 1,
8199
+ opacity: credits2 <= minCredits ? 0.3 : 1,
8200
8200
  transition: "opacity 0.15s ease"
8201
8201
  },
8202
8202
  onClick: handleDecrement,
@@ -8254,7 +8254,7 @@ function BuyCreditsModal({
8254
8254
  style: {
8255
8255
  width: 24,
8256
8256
  height: 24,
8257
- opacity: credits >= maxCredits ? 0.3 : 1,
8257
+ opacity: credits2 >= maxCredits ? 0.3 : 1,
8258
8258
  transition: "opacity 0.15s ease"
8259
8259
  },
8260
8260
  onClick: handleIncrement,
@@ -8386,7 +8386,7 @@ function BuyCreditsModal({
8386
8386
  pointerEvents: isInvalid ? "none" : "auto",
8387
8387
  transition: "opacity 0.2s ease"
8388
8388
  },
8389
- onClick: () => onBuy(credits, price),
8389
+ onClick: () => onBuy(credits2, price),
8390
8390
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8391
8391
  "span",
8392
8392
  {
@@ -9126,7 +9126,7 @@ function PerUserLimitsView() {
9126
9126
  ] });
9127
9127
  }
9128
9128
 
9129
- // src/credit-allocations-view.tsx
9129
+ // src/shared-credits-view.tsx
9130
9130
  var import_react21 = require("react");
9131
9131
  var import_ui15 = require("@streamoid/ui");
9132
9132
 
@@ -9375,23 +9375,23 @@ function ConfirmActionModal({
9375
9375
  );
9376
9376
  }
9377
9377
 
9378
- // src/credit-allocation-modal.tsx
9378
+ // src/credit-pool-modal.tsx
9379
9379
  var import_react19 = require("react");
9380
9380
  var import_ui13 = require("@streamoid/ui");
9381
9381
  var import_icons13 = require("@streamoid/icons");
9382
9382
 
9383
- // src/credit-allocations.ts
9384
- var ALLOCATION_NOTE_MAX_LENGTH = 500;
9385
- var CANCEL_REASON_MAX_LENGTH = 500;
9386
- function canShowCreditAllocations(api, workspace, organization) {
9387
- return Boolean(api.fetchCreditAllocations) && Boolean(workspace?.organizationId || organization?.id);
9383
+ // src/credit-pool.ts
9384
+ var POOL_NOTE_MAX_LENGTH = 500;
9385
+ var REMOVE_REASON_MAX_LENGTH = 500;
9386
+ function canShowSharedCredits(api, workspace, organization) {
9387
+ return Boolean(api.fetchCreditPool) && Boolean(workspace?.organizationId || organization?.id);
9388
9388
  }
9389
- function cleanAllocationValueInput(raw) {
9389
+ function cleanPoolValueInput(raw) {
9390
9390
  const kept = raw.replace(/[^\d.]/g, "");
9391
9391
  const dot = kept.indexOf(".");
9392
9392
  return dot === -1 ? kept : kept.slice(0, dot + 1) + kept.slice(dot + 1).replace(/\./g, "");
9393
9393
  }
9394
- function validateAllocationValue(mode, raw, balance) {
9394
+ function validatePoolValue(mode, raw) {
9395
9395
  const trimmed = raw.trim();
9396
9396
  if (!trimmed) return { ok: false, reason: mode === "percent" ? "Enter a percentage" : "Enter a number of credits" };
9397
9397
  if (!/^\d+(\.\d{1,2})?$/.test(trimmed)) {
@@ -9400,64 +9400,68 @@ function validateAllocationValue(mode, raw, balance) {
9400
9400
  const value = Number(trimmed);
9401
9401
  if (!(value > 0)) return { ok: false, reason: "Enter a number greater than 0" };
9402
9402
  if (mode === "percent" && value > 100) return { ok: false, reason: "A percentage can be at most 100" };
9403
- if (mode === "fixed" && typeof balance === "number" && Number.isFinite(balance) && value > balance) {
9404
- return {
9405
- ok: false,
9406
- reason: `This workspace has ${formatCredits(balance)} credits available`
9407
- };
9408
- }
9409
9403
  return { ok: true, value };
9410
9404
  }
9411
- function validateAllocationText(raw, max) {
9405
+ function validatePoolText(raw, max) {
9412
9406
  const value = raw.trim();
9413
9407
  if (!value) return { ok: false, reason: "Required" };
9414
9408
  if (value.length > max) return { ok: false, reason: `At most ${max} characters` };
9415
9409
  return { ok: true, value };
9416
9410
  }
9417
- function formatAllocationSplit(split) {
9411
+ var credits = (value) => `${formatCredits(value)} ${value === 1 ? "credit" : "credits"}`;
9412
+ function formatPoolSplit(split) {
9418
9413
  if (split.mode === "percent") return `${formatCredits(split.value)}%`;
9419
- return `${formatCredits(split.value)} ${split.value === 1 ? "credit" : "credits"}`;
9414
+ return credits(split.value);
9420
9415
  }
9421
- function formatAllocationRepeat(recurring) {
9422
- return recurring ? "Every renewal" : "Once";
9416
+ function describeAllowance(member) {
9417
+ const allowance = `${credits(member.allowance)} this cycle`;
9418
+ return member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 ${allowance}` : allowance;
9423
9419
  }
9424
- function describeAllocation(allocation) {
9425
- const split = formatAllocationSplit(allocation);
9426
- if (!allocation.recurring) return `${split} once`;
9427
- return allocation.mode === "percent" ? `${split} of each renewal` : `${split} every renewal`;
9428
- }
9429
- function allocationWorkspaceName(name) {
9420
+ function poolWorkspaceName(name) {
9430
9421
  return name?.trim() || "Unknown workspace";
9431
9422
  }
9432
- function initialTransferWarning(allocation) {
9433
- const transfer = allocation.initialTransfer;
9434
- if (transfer?.status !== "failed") return null;
9435
- const why = transfer.error?.trim();
9436
- return `The first transfer failed${why ? `: ${why}` : ""}. The allocation is active and renewals will still split, but these credits did not move.`;
9437
- }
9438
- function initialTransferSummary(allocation) {
9439
- const transfer = allocation.initialTransfer;
9440
- if (transfer?.status !== "done") return null;
9441
- return `Sent ${formatCredits(transfer.credits)} ${transfer.credits === 1 ? "credit" : "credits"} at setup`;
9442
- }
9443
- function cancelOutcomeWarning(result, targetName) {
9444
- const stuck = !result.targetMovedToFree && !result.reason?.trim() && result.allocation?.targetPlanChanged === true;
9445
- if (!result.error && !stuck) return null;
9446
- 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.`;
9447
- }
9448
- function cancelOutcomeSummary(result, targetName) {
9449
- const why = result.reason?.trim();
9450
- if (result.targetMovedToFree) return `Allocation canceled. ${targetName} is back on the free plan.`;
9451
- return why ? `Allocation canceled. ${why.replace(/\.?$/, ".")}` : "Allocation canceled";
9452
- }
9453
- var ALLOCATION_ERROR_CODES = [
9454
- "INSUFFICIENT_CREDITS",
9455
- "SOURCE_ON_FREE_PLAN",
9456
- "ALLOCATION_NOT_ALLOWED",
9457
- "TARGET_PLAN_CHANGE_FAILED"
9458
- ];
9459
- function findAllocationError(failure) {
9460
- const error = findBillingError(failure, ALLOCATION_ERROR_CODES);
9423
+ function sharedPoolBannerText(pool) {
9424
+ const sponsor = pool.sponsorName?.trim() || "another workspace";
9425
+ return `Credits shared by ${sponsor}: ${formatCredits(pool.remaining)} of ${formatCredits(pool.allowance)} left this cycle`;
9426
+ }
9427
+ function pausedOwnCreditsText(pool) {
9428
+ const own = pool.ownCredits;
9429
+ if (typeof own !== "number" || !Number.isFinite(own)) {
9430
+ return "This workspace spends these shared credits, not its own; credits bought here can't be used while it does.";
9431
+ }
9432
+ return `This workspace's own ${credits(own)} ${own === 1 ? "is" : "are"} paused while it uses shared credits. They stay with it and can be used again once sharing stops.`;
9433
+ }
9434
+ function creditsMeter({
9435
+ availableCredits,
9436
+ totalCredits,
9437
+ sharedPool
9438
+ }) {
9439
+ if (sharedPool) {
9440
+ const used2 = Math.max(0, sharedPool.used);
9441
+ const remaining = Math.max(0, sharedPool.remaining);
9442
+ const progressPct2 = sharedPool.allowance > 0 ? Math.min(100, Math.round(used2 / sharedPool.allowance * 100)) : 100;
9443
+ return { used: used2, remaining, progressPct: progressPct2, remainingPct: 100 - progressPct2 };
9444
+ }
9445
+ const used = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
9446
+ const progressPct = used != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(used / totalCredits * 100)) : 0;
9447
+ return { used, remaining: availableCredits, progressPct, remainingPct: 100 - progressPct };
9448
+ }
9449
+ function usedByWorkspaceKey(log, currentWorkspaceId) {
9450
+ const usedBy = log.meta?.usedBy;
9451
+ if (typeof usedBy !== "string" || !usedBy) return null;
9452
+ return usedBy.toLowerCase() === String(currentWorkspaceId ?? "").toLowerCase() ? null : usedBy;
9453
+ }
9454
+ function usedByNamesFrom(pool) {
9455
+ const names = {};
9456
+ for (const member of [...pool?.formerMembers ?? [], ...pool?.members ?? []]) {
9457
+ const name = member.memberWorkspaceName?.trim();
9458
+ if (name && member.memberWorkspaceKey) names[member.memberWorkspaceKey.toLowerCase()] = name;
9459
+ }
9460
+ return names;
9461
+ }
9462
+ var POOL_ERROR_CODES = ["SPONSOR_ON_FREE_PLAN", "POOL_NOT_ALLOWED"];
9463
+ function findPoolError(failure) {
9464
+ const error = findBillingError(failure, POOL_ERROR_CODES);
9461
9465
  if (!error) return null;
9462
9466
  const status = error.status;
9463
9467
  const message = [status?.message, error.error, error.message].find(
@@ -9466,29 +9470,18 @@ function findAllocationError(failure) {
9466
9470
  const details = error.details && typeof error.details === "object" ? error.details : {};
9467
9471
  return { code: error.code, details, message: message ?? null };
9468
9472
  }
9469
- var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
9470
- function allocationErrorMessage(failure) {
9471
- const found = findAllocationError(failure);
9473
+ function poolErrorMessage(failure) {
9474
+ const found = findPoolError(failure);
9472
9475
  if (!found) return null;
9473
9476
  switch (found.code) {
9474
- case "INSUFFICIENT_CREDITS": {
9475
- const balance = num(found.details.currentBalance);
9476
- const required = num(found.details.requiredCredits);
9477
- if (balance !== null && required !== null) {
9478
- return `Not enough credits: this would send ${formatCredits(required)} now, but the workspace has ${formatCredits(balance)}.`;
9479
- }
9480
- return "This workspace doesn't have enough credits to send that now.";
9481
- }
9482
- case "SOURCE_ON_FREE_PLAN":
9477
+ case "SPONSOR_ON_FREE_PLAN":
9483
9478
  return "A workspace on the free plan can't share credits. Upgrade this workspace's plan first.";
9484
- case "ALLOCATION_NOT_ALLOWED":
9485
- return found.message ? found.message.replace(/\.?$/, ".") : "This allocation isn't allowed.";
9486
- case "TARGET_PLAN_CHANGE_FAILED":
9487
- 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.";
9479
+ case "POOL_NOT_ALLOWED":
9480
+ return found.message ? found.message.replace(/\.?$/, ".") : "Credits can't be shared with that workspace.";
9488
9481
  }
9489
9482
  }
9490
9483
 
9491
- // src/credit-allocation-modal.tsx
9484
+ // src/credit-pool-modal.tsx
9492
9485
  var import_jsx_runtime25 = require("react/jsx-runtime");
9493
9486
  var MODAL_T = {
9494
9487
  primary: "var(--alias-text---icons-primary)",
@@ -9521,7 +9514,7 @@ var modalBodyText = {
9521
9514
  fontSize: T16.fsSm,
9522
9515
  lineHeight: T16.lhSm
9523
9516
  };
9524
- function AllocationModalShell({
9517
+ function PoolModalShell({
9525
9518
  title,
9526
9519
  busy,
9527
9520
  onClose,
@@ -9622,7 +9615,7 @@ function AllocationModalShell({
9622
9615
  }
9623
9616
  );
9624
9617
  }
9625
- function AllocationWarning({ children }) {
9618
+ function PoolWarning({ children }) {
9626
9619
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9627
9620
  "div",
9628
9621
  {
@@ -9641,68 +9634,55 @@ function AllocationWarning({ children }) {
9641
9634
  );
9642
9635
  }
9643
9636
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9644
- function CreditAllocationModal(props) {
9637
+ function CreditPoolMemberModal(props) {
9645
9638
  const { toast } = useSettingsAdapters();
9646
- const editing = props.kind === "edit" ? props.allocation : null;
9647
- const [targetId, setTargetId] = (0, import_react19.useState)("");
9648
- const [mode, setMode] = (0, import_react19.useState)(editing?.mode ?? "fixed");
9639
+ const editing = props.kind === "edit" ? props.member : null;
9640
+ const [memberId, setMemberId] = (0, import_react19.useState)("");
9641
+ const [mode, setMode] = (0, import_react19.useState)(editing?.mode ?? "percent");
9649
9642
  const [raw, setRaw] = (0, import_react19.useState)(editing ? String(editing.value) : "");
9650
- const [recurring, setRecurring] = (0, import_react19.useState)(editing?.recurring ?? true);
9651
9643
  const [note, setNote] = (0, import_react19.useState)("");
9652
9644
  const [touched, setTouched] = (0, import_react19.useState)(false);
9653
9645
  const [saving, setSaving] = (0, import_react19.useState)(false);
9654
9646
  const [serverError, setServerError] = (0, import_react19.useState)(null);
9655
9647
  (0, import_react19.useEffect)(() => {
9656
9648
  setServerError(null);
9657
- }, [targetId, mode, raw, recurring, note]);
9658
- const balance = props.kind === "create" ? props.balance : null;
9659
- const value = validateAllocationValue(mode, raw, props.kind === "create" ? balance : null);
9660
- const noteCheck = validateAllocationText(note, ALLOCATION_NOTE_MAX_LENGTH);
9661
- const splitChanged = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9662
- const changed = !editing || splitChanged || recurring !== editing.recurring;
9663
- const valid = value.ok && (props.kind === "edit" || Boolean(targetId) && noteCheck.ok);
9649
+ }, [memberId, mode, raw, note]);
9650
+ const value = validatePoolValue(mode, raw);
9651
+ const noteCheck = validatePoolText(note, POOL_NOTE_MAX_LENGTH);
9652
+ const changed = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9653
+ const valid = value.ok && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9664
9654
  const canSave = !saving && changed && valid;
9665
9655
  const valueHint = touched && !value.ok ? value.reason : null;
9666
- const percentNow = props.kind === "create" && mode === "percent" && value.ok && balance !== null ? Math.floor(roundCredits(balance * value.value / 100) * 100) / 100 : null;
9667
9656
  const handleSave = async () => {
9668
9657
  if (!canSave || !value.ok) return;
9669
9658
  setSaving(true);
9670
9659
  try {
9671
- if (props.kind === "create") {
9672
- const created = await props.onSave({
9673
- targetWorkspaceId: targetId,
9660
+ if (props.kind === "add") {
9661
+ const added = await props.onSave({
9662
+ memberWorkspaceId: memberId,
9674
9663
  mode,
9675
9664
  value: value.value,
9676
- recurring,
9677
9665
  note: noteCheck.ok ? noteCheck.value : ""
9678
9666
  });
9679
- const warning = initialTransferWarning(created);
9680
- if (warning) toast.error(warning);
9681
- else {
9682
- const sent = initialTransferSummary(created);
9683
- toast.success(sent ? `Allocation created. ${sent}.` : "Allocation created");
9684
- }
9667
+ toast.success(`Credits shared. ${describeAllowance(added)}.`);
9685
9668
  } else {
9686
- await props.onSave(
9687
- splitChanged ? { mode, value: value.value, recurring } : { recurring }
9688
- );
9689
- toast.success("Allocation updated. Applies from the next renewal.");
9669
+ const updated = await props.onSave({ mode, value: value.value });
9670
+ toast.success(`Share updated. ${describeAllowance(updated)}.`);
9690
9671
  }
9691
9672
  props.onSaved();
9692
9673
  props.onClose();
9693
9674
  } catch (err) {
9694
- const message = allocationErrorMessage(err);
9675
+ const message = poolErrorMessage(err);
9695
9676
  if (message) setServerError(message);
9696
- else toast.error(errorMessage(err, { method: props.kind === "create" ? "POST" : "PATCH" }));
9697
- if (findAllocationError(err)?.details.allocationId) props.onSaved();
9677
+ else toast.error(errorMessage(err, { method: props.kind === "add" ? "POST" : "PATCH" }));
9698
9678
  setSaving(false);
9699
9679
  }
9700
9680
  };
9701
- const targetOptions = props.kind === "create" ? props.targets.map((target) => ({ id: target.id, label: target.name })) : [];
9681
+ const candidateOptions = props.kind === "add" ? props.candidates.map((candidate) => ({ id: candidate.id, label: candidate.name })) : [];
9702
9682
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
9703
- AllocationModalShell,
9683
+ PoolModalShell,
9704
9684
  {
9705
- title: props.kind === "create" ? "New credit allocation" : "Edit credit allocation",
9685
+ title: props.kind === "add" ? "Share credits" : "Edit shared credits",
9706
9686
  busy: saving,
9707
9687
  onClose: props.onClose,
9708
9688
  footer: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
@@ -9719,7 +9699,7 @@ function CreditAllocationModal(props) {
9719
9699
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9720
9700
  import_ui13.ScButton,
9721
9701
  {
9722
- text: props.kind === "create" ? "Create allocation" : "Save",
9702
+ text: props.kind === "add" ? "Share credits" : "Save",
9723
9703
  variant: "primary",
9724
9704
  size: "md",
9725
9705
  state: canSave ? "default" : "disabled",
@@ -9729,29 +9709,29 @@ function CreditAllocationModal(props) {
9729
9709
  ] }),
9730
9710
  children: [
9731
9711
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9732
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Send credits to" }),
9733
- props.kind === "create" ? targetOptions.length ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9712
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Share with" }),
9713
+ props.kind === "add" ? candidateOptions.length ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9734
9714
  import_ui13.ScSelect,
9735
9715
  {
9736
- value: targetId,
9737
- onChange: setTargetId,
9738
- options: targetOptions,
9716
+ value: memberId,
9717
+ onChange: setMemberId,
9718
+ options: candidateOptions,
9739
9719
  placeholder: "Choose a workspace",
9740
9720
  disabled: saving
9741
9721
  }
9742
- ) : /* @__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 })
9722
+ ) : /* @__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 })
9743
9723
  ] }),
9744
9724
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spMd }, children: [
9745
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "How much" }),
9725
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "How much, each billing cycle" }),
9746
9726
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-center", style: { gap: T16.sp3xl }, children: [
9747
9727
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9748
9728
  import_ui13.ScPairtext,
9749
9729
  {
9750
9730
  type: "radio",
9751
- content: "Fixed credits",
9752
- checked: mode === "fixed",
9731
+ content: "Percentage",
9732
+ checked: mode === "percent",
9753
9733
  onChange: () => {
9754
- if (!saving) setMode("fixed");
9734
+ if (!saving) setMode("percent");
9755
9735
  }
9756
9736
  }
9757
9737
  ),
@@ -9759,10 +9739,10 @@ function CreditAllocationModal(props) {
9759
9739
  import_ui13.ScPairtext,
9760
9740
  {
9761
9741
  type: "radio",
9762
- content: "Percentage",
9763
- checked: mode === "percent",
9742
+ content: "Fixed credits",
9743
+ checked: mode === "fixed",
9764
9744
  onChange: () => {
9765
- if (!saving) setMode("percent");
9745
+ if (!saving) setMode("fixed");
9766
9746
  }
9767
9747
  }
9768
9748
  )
@@ -9790,7 +9770,7 @@ function CreditAllocationModal(props) {
9790
9770
  value: raw,
9791
9771
  disabled: saving,
9792
9772
  onChange: (e) => {
9793
- setRaw(cleanAllocationValueInput(e.target.value));
9773
+ setRaw(cleanPoolValueInput(e.target.value));
9794
9774
  setTouched(true);
9795
9775
  },
9796
9776
  className: "flex-1 min-w-0",
@@ -9808,39 +9788,18 @@ function CreditAllocationModal(props) {
9808
9788
  ]
9809
9789
  }
9810
9790
  ),
9811
- 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." })
9812
- ] }),
9813
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-start justify-between w-full", style: { gap: T16.sp3xl }, children: [
9814
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: T16.spXs }, children: [
9815
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Repeat on every renewal" }),
9816
- /* @__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." })
9817
- ] }),
9818
- /* @__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)(
9819
- import_ui13.ScToggleSwitch,
9820
- {
9821
- checked: recurring,
9822
- disabled: saving,
9823
- onToggle: (next) => setRecurring(next),
9824
- role: "switch",
9825
- "aria-checked": recurring,
9826
- "aria-label": "Repeat on every renewal",
9827
- tabIndex: 0,
9828
- onKeyDown: (event) => {
9829
- if ((event.key === " " || event.key === "Enter") && !saving) {
9830
- event.preventDefault();
9831
- setRecurring(!recurring);
9832
- }
9833
- }
9834
- }
9835
- ) })
9791
+ 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: [
9792
+ 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.",
9793
+ editing ? " Applies immediately." : ""
9794
+ ] })
9836
9795
  ] }),
9837
- props.kind === "create" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9796
+ props.kind === "add" && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9838
9797
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Note" }),
9839
9798
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9840
9799
  import_ui13.ScTextArea,
9841
9800
  {
9842
9801
  value: note,
9843
- maxLength: ALLOCATION_NOTE_MAX_LENGTH,
9802
+ maxLength: POOL_NOTE_MAX_LENGTH,
9844
9803
  placeholder: "Why are these credits being shared?",
9845
9804
  disabled: saving,
9846
9805
  onChange: (e) => setNote(e.target.value),
@@ -9852,117 +9811,57 @@ function CreditAllocationModal(props) {
9852
9811
  "Required. ",
9853
9812
  note.trim().length,
9854
9813
  "/",
9855
- ALLOCATION_NOTE_MAX_LENGTH
9814
+ POOL_NOTE_MAX_LENGTH
9856
9815
  ] })
9857
9816
  ] }),
9858
- 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." }),
9817
+ 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." }),
9859
9818
  serverError && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
9860
9819
  ]
9861
9820
  }
9862
9821
  );
9863
9822
  }
9864
9823
 
9865
- // src/cancel-credit-allocation-modal.tsx
9824
+ // src/remove-pool-member-modal.tsx
9866
9825
  var import_react20 = require("react");
9867
9826
  var import_ui14 = require("@streamoid/ui");
9868
9827
  var import_jsx_runtime26 = require("react/jsx-runtime");
9869
- function CancelCreditAllocationModal({
9870
- allocation,
9871
- targetName,
9872
- onCancel,
9828
+ function RemovePoolMemberModal({
9829
+ member,
9830
+ memberName,
9831
+ sponsorName,
9832
+ onRemove,
9873
9833
  onClose,
9874
- onCanceled
9834
+ onRemoved
9875
9835
  }) {
9876
9836
  const { toast } = useSettingsAdapters();
9877
9837
  const [reason, setReason] = (0, import_react20.useState)("");
9878
9838
  const [saving, setSaving] = (0, import_react20.useState)(false);
9879
- const [outcome, setOutcome] = (0, import_react20.useState)(null);
9880
- const check = validateAllocationText(reason, CANCEL_REASON_MAX_LENGTH);
9839
+ const check = validatePoolText(reason, REMOVE_REASON_MAX_LENGTH);
9881
9840
  const canConfirm = !saving && check.ok;
9882
9841
  const handleConfirm = async () => {
9883
9842
  if (!canConfirm || !check.ok) return;
9884
9843
  setSaving(true);
9885
9844
  try {
9886
- const result = await onCancel(check.value);
9887
- const warning = cancelOutcomeWarning(
9888
- { ...result, allocation: result.allocation ?? allocation },
9889
- targetName
9890
- );
9891
- onCanceled({ planNotRestored: Boolean(warning) });
9892
- if (warning) {
9893
- setOutcome({ warning, error: result.error });
9894
- setSaving(false);
9895
- return;
9896
- }
9897
- toast.success(cancelOutcomeSummary(result, targetName));
9845
+ await onRemove(check.value);
9846
+ toast.success(`${memberName} can no longer spend ${sponsorName}'s credits`);
9847
+ onRemoved();
9898
9848
  onClose();
9899
9849
  } catch (err) {
9900
9850
  toast.error(errorMessage(err, { method: "DELETE" }));
9901
9851
  setSaving(false);
9902
9852
  }
9903
9853
  };
9904
- const copyId = () => {
9905
- const id = allocation.targetWorkspaceKey;
9906
- const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
9907
- if (!clipboard) {
9908
- toast.error("Couldn't copy. Select the ID and copy it instead.");
9909
- return;
9910
- }
9911
- clipboard.writeText(id).then(
9912
- () => toast.success("Workspace ID copied"),
9913
- () => toast.error("Couldn't copy. Select the ID and copy it instead.")
9914
- );
9915
- };
9916
- if (outcome) {
9917
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
9918
- AllocationModalShell,
9919
- {
9920
- title: "Target plan not restored",
9921
- busy: false,
9922
- onClose,
9923
- footer: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui14.ScButton, { text: "Got it", variant: "primary", size: "md", onClick: onClose }),
9924
- children: [
9925
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AllocationWarning, { children: outcome.warning }),
9926
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9927
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Workspace ID" }),
9928
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center w-full", style: { gap: MODAL_T.spXl }, children: [
9929
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9930
- "code",
9931
- {
9932
- className: "flex-1 min-w-0 truncate",
9933
- style: {
9934
- ...modalBodyText,
9935
- color: MODAL_T.primary,
9936
- background: MODAL_T.fillPlus,
9937
- borderRadius: MODAL_T.r3xl,
9938
- padding: `${MODAL_T.spXl} ${MODAL_T.sp3xl}`,
9939
- userSelect: "all"
9940
- },
9941
- children: allocation.targetWorkspaceKey
9942
- }
9943
- ),
9944
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui14.ScButton, { text: "Copy", variant: "secondary", size: "sm", onClick: copyId })
9945
- ] })
9946
- ] }),
9947
- outcome.error && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: MODAL_T.muted }, children: [
9948
- "Details: ",
9949
- outcome.error
9950
- ] })
9951
- ]
9952
- }
9953
- );
9954
- }
9955
9854
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
9956
- AllocationModalShell,
9855
+ PoolModalShell,
9957
9856
  {
9958
- title: "Cancel credit allocation",
9857
+ title: "Stop sharing credits",
9959
9858
  busy: saving,
9960
9859
  onClose,
9961
9860
  footer: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
9962
9861
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9963
9862
  import_ui14.ScButton,
9964
9863
  {
9965
- text: "Keep allocation",
9864
+ text: "Keep sharing",
9966
9865
  variant: "secondary",
9967
9866
  size: "md",
9968
9867
  state: saving ? "disabled" : "default",
@@ -9972,7 +9871,7 @@ function CancelCreditAllocationModal({
9972
9871
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9973
9872
  import_ui14.ScButton,
9974
9873
  {
9975
- text: "Cancel allocation",
9874
+ text: "Stop sharing",
9976
9875
  variant: "error",
9977
9876
  size: "md",
9978
9877
  state: canConfirm ? "default" : "disabled",
@@ -9982,16 +9881,18 @@ function CancelCreditAllocationModal({
9982
9881
  ] }),
9983
9882
  children: [
9984
9883
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: MODAL_T.primary }, children: [
9985
- "Stop sending ",
9986
- describeAllocation(allocation),
9987
- " to ",
9988
- targetName,
9989
- "?"
9884
+ "This stops ",
9885
+ memberName,
9886
+ " spending ",
9887
+ sponsorName,
9888
+ "'s credits immediately."
9990
9889
  ] }),
9991
9890
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { style: { ...modalBodyText, color: MODAL_T.tertiary }, children: [
9992
- targetName,
9993
- " keeps the credits it already received until they expire.",
9994
- 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." : ""
9891
+ "It had ",
9892
+ describeAllowance(member),
9893
+ " and has used ",
9894
+ formatCredits(member.used),
9895
+ ". Credits it already spent are not returned; anything it runs from now uses its own credits."
9995
9896
  ] }),
9996
9897
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9997
9898
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Reason" }),
@@ -9999,8 +9900,8 @@ function CancelCreditAllocationModal({
9999
9900
  import_ui14.ScTextArea,
10000
9901
  {
10001
9902
  value: reason,
10002
- maxLength: CANCEL_REASON_MAX_LENGTH,
10003
- placeholder: "Why is this allocation ending?",
9903
+ maxLength: REMOVE_REASON_MAX_LENGTH,
9904
+ placeholder: "Why is this workspace no longer sharing these credits?",
10004
9905
  disabled: saving,
10005
9906
  onChange: (e) => setReason(e.target.value),
10006
9907
  rows: 3,
@@ -10014,16 +9915,13 @@ function CancelCreditAllocationModal({
10014
9915
  );
10015
9916
  }
10016
9917
 
10017
- // src/credit-allocations-view.tsx
9918
+ // src/shared-credits-view.tsx
10018
9919
  var import_jsx_runtime27 = require("react/jsx-runtime");
10019
9920
  var T17 = {
10020
9921
  primary: "var(--alias-text---icons-primary)",
10021
9922
  muted: "var(--alias-text---icons-muted)",
10022
9923
  tertiary: "var(--alias-text---icons-tertiary)",
10023
9924
  warning: "var(--alias-text---icons-warning)",
10024
- success: "var(--alias-text---icons-success)",
10025
- warningFill: "var(--alias-fill-warning-soft)",
10026
- warningBorder: "var(--alias-border-warning)",
10027
9925
  fillPlus: "var(--alias-fill-neutral-neutralplus)",
10028
9926
  surfaceBase: "var(--alias-surface-base)",
10029
9927
  borderSubtle: "var(--alias-border-subtle)",
@@ -10037,9 +9935,7 @@ var T17 = {
10037
9935
  r3xl: "var(--radius-3xl)",
10038
9936
  rFull: "var(--radius-full)"
10039
9937
  };
10040
- var GIVING_COLUMNS = "minmax(220px, 1fr) 120px 120px 150px 110px 150px";
10041
- var TRANSFER_COLUMNS = "120px 170px minmax(200px, 1fr) 120px";
10042
- var TRANSFERS_PAGE = 20;
9938
+ var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
10043
9939
  var cellText2 = {
10044
9940
  fontFamily: T17.font,
10045
9941
  fontSize: T17.fsSm,
@@ -10061,138 +9957,67 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ (0, import_jsx_runtime27.
10061
9957
  children: labels.map((label) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: label }, label))
10062
9958
  }
10063
9959
  );
10064
- function Badge({ text: text2, tone }) {
10065
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10066
- "span",
10067
- {
10068
- className: "inline-flex items-center shrink-0",
10069
- style: {
10070
- ...cellText2,
10071
- fontSize: 12,
10072
- padding: "0 8px",
10073
- height: 22,
10074
- borderRadius: T17.rFull,
10075
- color: tone === "warning" ? T17.primary : T17.muted,
10076
- background: tone === "warning" ? T17.warningFill : T17.fillPlus,
10077
- border: tone === "warning" ? `1px solid ${T17.warningBorder}` : "1px solid transparent"
10078
- },
10079
- children: text2
10080
- }
10081
- );
10082
- }
10083
- function GivingRow({
10084
- allocation,
10085
- planNotRestored,
9960
+ function MemberRow2({
9961
+ member,
10086
9962
  onEdit,
10087
- onCancel
9963
+ onRemove
10088
9964
  }) {
10089
- const failed = initialTransferWarning(allocation);
10090
- const sent = initialTransferSummary(allocation);
10091
- const active = allocation.status === "active";
9965
+ const spent = member.remaining <= 0;
10092
9966
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10093
9967
  "div",
10094
9968
  {
10095
9969
  style: {
10096
9970
  borderBottom: `1px solid ${T17.borderSubtle}`,
10097
9971
  display: "grid",
10098
- gridTemplateColumns: GIVING_COLUMNS,
9972
+ gridTemplateColumns: MEMBER_COLUMNS2,
10099
9973
  alignItems: "center",
10100
9974
  padding: `10px ${T17.sp3xl}`,
10101
9975
  gap: 24
10102
9976
  },
10103
9977
  children: [
10104
9978
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10105
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: allocationWorkspaceName(allocation.targetWorkspaceName) }),
10106
- failed ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col", style: { gap: 2 }, children: [
10107
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { text: "First transfer failed", tone: "warning" }) }),
10108
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: failed })
10109
- ] }) : sent ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: sent }) : null,
10110
- allocation.note && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", title: allocation.note, style: { ...cellText2, color: T17.tertiary }, children: allocation.note })
10111
- ] }),
10112
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: formatAllocationSplit(allocation) }),
10113
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: formatAllocationRepeat(allocation.recurring) }),
10114
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-wrap items-center", style: { gap: 6 }, children: [
10115
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { text: active ? "Active" : "Canceled", tone: "neutral" }),
10116
- planNotRestored && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Badge, { text: "Plan not restored", tone: "warning" })
9979
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: poolWorkspaceName(member.memberWorkspaceName) }),
9980
+ member.note && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", title: member.note, style: { ...cellText2, color: T17.tertiary }, children: member.note })
10117
9981
  ] }),
10118
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(allocation.createdAt) }),
9982
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: describeAllowance(member) }),
9983
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: cellText2, children: formatCredits(member.used) }),
9984
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: spent ? T17.warning : T17.primary }, children: formatCredits(member.remaining) }),
10119
9985
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center", style: { gap: 8 }, children: [
10120
- active && onEdit && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
10121
- active && onCancel && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Cancel", variant: "secondary", size: "sm", onClick: onCancel })
9986
+ onEdit && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
9987
+ onRemove && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Remove", variant: "secondary", size: "sm", onClick: onRemove })
10122
9988
  ] })
10123
9989
  ]
10124
9990
  }
10125
9991
  );
10126
9992
  }
10127
- function TransferRow({ transfer }) {
10128
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10129
- "div",
10130
- {
10131
- style: {
10132
- borderBottom: `1px solid ${T17.borderSubtle}`,
10133
- display: "grid",
10134
- gridTemplateColumns: TRANSFER_COLUMNS,
10135
- alignItems: "center",
10136
- padding: `8px ${T17.sp3xl}`,
10137
- gap: 24
10138
- },
10139
- children: [
10140
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(transfer.createdAt) }),
10141
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: cellText2, children: [
10142
- transfer.direction === "sent" ? "Sent" : "Received",
10143
- " \xB7",
10144
- " ",
10145
- transfer.kind === "initial" ? "Initial" : "Renewal"
10146
- ] }),
10147
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "truncate", style: cellText2, children: transfer.counterpartWorkspaceName ?? (transfer.counterpartWorkspaceId ? "Unknown workspace" : "\u2014") }),
10148
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, color: transfer.direction === "received" ? T17.success : T17.primary }, children: [
10149
- transfer.direction === "received" ? "+" : "\u2212",
10150
- formatCredits(transfer.credits)
10151
- ] })
10152
- ]
10153
- }
10154
- );
10155
- }
10156
- function CreditAllocationsView() {
9993
+ function SharedCreditsView() {
10157
9994
  const { api, workspace, toast } = useSettingsAdapters();
10158
9995
  const workspaceId = workspace.current?.id ?? null;
9996
+ const workspaceName = workspace.current?.name?.trim() || "this workspace";
10159
9997
  const [data, setData] = (0, import_react21.useState)(null);
10160
9998
  const [loading, setLoading] = (0, import_react21.useState)(true);
10161
9999
  const [failed, setFailed] = (0, import_react21.useState)(false);
10162
- const [targets, setTargets] = (0, import_react21.useState)([]);
10163
- const [transfers, setTransfers] = (0, import_react21.useState)([]);
10164
- const [transfersTotal, setTransfersTotal] = (0, import_react21.useState)(0);
10165
- const [transfersPage, setTransfersPage] = (0, import_react21.useState)(0);
10166
- const [transfersFailed, setTransfersFailed] = (0, import_react21.useState)(false);
10167
- const [loadingMore, setLoadingMore] = (0, import_react21.useState)(false);
10168
- const [creating, setCreating] = (0, import_react21.useState)(false);
10000
+ const [candidates, setCandidates] = (0, import_react21.useState)([]);
10001
+ const [adding, setAdding] = (0, import_react21.useState)(false);
10169
10002
  const [editing, setEditing] = (0, import_react21.useState)(null);
10170
- const [canceling, setCanceling] = (0, import_react21.useState)(null);
10171
- const [planNotRestored, setPlanNotRestored] = (0, import_react21.useState)(() => /* @__PURE__ */ new Set());
10003
+ const [removing, setRemoving] = (0, import_react21.useState)(null);
10172
10004
  const latestLoad = (0, import_react21.useRef)(0);
10173
10005
  const load = (0, import_react21.useCallback)(
10174
10006
  async ({ refresh = false } = {}) => {
10175
10007
  const loadId = ++latestLoad.current;
10176
- if (!api.fetchCreditAllocations) {
10008
+ if (!api.fetchCreditPool) {
10177
10009
  setLoading(false);
10178
10010
  setFailed(true);
10179
10011
  return;
10180
10012
  }
10181
- const [next, page] = await Promise.all([
10182
- api.fetchCreditAllocations().catch(() => null),
10183
- api.fetchCreditTransfers ? api.fetchCreditTransfers({ page: 1, limit: TRANSFERS_PAGE }).catch(() => null) : Promise.resolve(null)
10184
- ]);
10013
+ const next = await api.fetchCreditPool().catch(() => null);
10185
10014
  if (loadId !== latestLoad.current) return;
10186
10015
  if (refresh && !next) {
10187
- toast.error("Couldn't refresh credit allocations. Reopen this page to see the latest.");
10016
+ toast.error("Couldn't refresh shared credits. Reopen this page to see the latest.");
10188
10017
  return;
10189
10018
  }
10190
10019
  setFailed(!next);
10191
10020
  setData(next);
10192
- setTransfersFailed(Boolean(api.fetchCreditTransfers) && !page);
10193
- setTransfers(page?.transfers ?? []);
10194
- setTransfersTotal(page?.total ?? 0);
10195
- setTransfersPage(page ? 1 : 0);
10196
10021
  setLoading(false);
10197
10022
  },
10198
10023
  [api, toast]
@@ -10201,11 +10026,9 @@ function CreditAllocationsView() {
10201
10026
  setLoading(true);
10202
10027
  setFailed(false);
10203
10028
  setData(null);
10204
- setTransfers([]);
10205
- setCreating(false);
10029
+ setAdding(false);
10206
10030
  setEditing(null);
10207
- setCanceling(null);
10208
- setPlanNotRestored(/* @__PURE__ */ new Set());
10031
+ setRemoving(null);
10209
10032
  void load();
10210
10033
  }, [workspaceId]);
10211
10034
  (0, import_react21.useEffect)(
@@ -10215,36 +10038,21 @@ function CreditAllocationsView() {
10215
10038
  []
10216
10039
  );
10217
10040
  const canManage = Boolean(data?.canManage);
10218
- const canCreate = canManage && Boolean(api.createCreditAllocation);
10219
- const openCreate = async () => {
10220
- if (!api.fetchAllocationTargets) {
10221
- setTargets([]);
10222
- setCreating(true);
10041
+ const sponsor = data?.sponsor ?? null;
10042
+ const canAdd = canManage && !sponsor && Boolean(api.addCreditPoolMember);
10043
+ const openAdd = async () => {
10044
+ if (!api.fetchCreditPoolCandidates) {
10045
+ setCandidates([]);
10046
+ setAdding(true);
10223
10047
  return;
10224
10048
  }
10225
10049
  try {
10226
- setTargets(await api.fetchAllocationTargets());
10227
- setCreating(true);
10050
+ setCandidates(await api.fetchCreditPoolCandidates());
10051
+ setAdding(true);
10228
10052
  } catch {
10229
10053
  toast.error("Couldn't load this organization's workspaces. Try again.");
10230
10054
  }
10231
10055
  };
10232
- const loadMoreTransfers = async () => {
10233
- if (!api.fetchCreditTransfers || loadingMore) return;
10234
- const loadId = latestLoad.current;
10235
- setLoadingMore(true);
10236
- try {
10237
- const page = await api.fetchCreditTransfers({ page: transfersPage + 1, limit: TRANSFERS_PAGE });
10238
- if (loadId !== latestLoad.current) return;
10239
- setTransfers((prev) => [...prev, ...page.transfers]);
10240
- setTransfersTotal(page.total);
10241
- setTransfersPage(transfersPage + 1);
10242
- } catch {
10243
- toast.error("Couldn't load more transfers. Try again.");
10244
- } finally {
10245
- setLoadingMore(false);
10246
- }
10247
- };
10248
10056
  if (loading) {
10249
10057
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10250
10058
  "div",
@@ -10267,9 +10075,7 @@ function CreditAllocationsView() {
10267
10075
  }
10268
10076
  );
10269
10077
  }
10270
- const giving = data?.giving ?? [];
10271
- const sponsor = data?.receiving.find((a) => a.status === "active") ?? null;
10272
- const balance = typeof workspace.current?.availableCredits === "number" ? workspace.current.availableCredits : null;
10078
+ const members = data?.members ?? [];
10273
10079
  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: [
10274
10080
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start justify-between shrink-0 w-full", style: { gap: 12 }, children: [
10275
10081
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 4 }, children: [
@@ -10277,12 +10083,12 @@ function CreditAllocationsView() {
10277
10083
  "span",
10278
10084
  {
10279
10085
  style: { fontFamily: T17.font, fontSize: T17.fsXl, fontWeight: 500, lineHeight: T17.lhXl, color: T17.primary },
10280
- children: "Credit allocations"
10086
+ children: "Shared credits"
10281
10087
  }
10282
10088
  ),
10283
- /* @__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." })
10089
+ /* @__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." })
10284
10090
  ] }),
10285
- canCreate && !failed && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "New allocation", variant: "primary", size: "md", onClick: () => void openCreate() })
10091
+ canAdd && !failed && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ui15.ScButton, { text: "Share credits", variant: "primary", size: "md", onClick: () => void openAdd() })
10286
10092
  ] }),
10287
10093
  sponsor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10288
10094
  "div",
@@ -10296,103 +10102,74 @@ function CreditAllocationsView() {
10296
10102
  borderRadius: T17.r3xl
10297
10103
  },
10298
10104
  children: [
10299
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, fontWeight: 500 }, children: [
10300
- "Funded by ",
10301
- allocationWorkspaceName(sponsor.sourceWorkspaceName)
10302
- ] }),
10105
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: sharedPoolBannerText({
10106
+ sponsorName: poolWorkspaceName(sponsor.sponsorWorkspaceName),
10107
+ allowance: sponsor.allowance,
10108
+ remaining: sponsor.remaining
10109
+ }) }),
10303
10110
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, color: T17.muted }, children: [
10304
- "Receives ",
10305
- describeAllocation(sponsor),
10111
+ "This workspace spends ",
10112
+ poolWorkspaceName(sponsor.sponsorWorkspaceName),
10113
+ "'s credits, not its own",
10114
+ sponsor.cycle?.end ? `, until its allowance starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10306
10115
  "."
10307
10116
  ] })
10308
10117
  ]
10309
10118
  }
10310
10119
  ),
10311
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10312
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: "Giving" }),
10120
+ !sponsor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10121
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: "Shared with" }),
10313
10122
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10314
10123
  MemberListFrame,
10315
10124
  {
10316
10125
  grow: "cap",
10317
10126
  maxHeight: 420,
10318
- contentMinWidth: 940,
10319
- header: tableHeader(GIVING_COLUMNS, ["Workspace", "Split", "Repeats", "Status", "Created", "Actions"]),
10320
- isEmpty: failed || giving.length === 0,
10321
- emptyText: failed ? "Couldn't load credit allocations. Try again later." : "This workspace doesn't send credits to any other workspace.",
10322
- children: giving.map((allocation) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10323
- GivingRow,
10127
+ contentMinWidth: 860,
10128
+ header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Actions"]),
10129
+ isEmpty: failed || members.length === 0,
10130
+ emptyText: failed ? "Couldn't load shared credits. Try again later." : "This workspace doesn't share its credits with any other workspace.",
10131
+ children: members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10132
+ MemberRow2,
10324
10133
  {
10325
- allocation,
10326
- planNotRestored: planNotRestored.has(allocation._id),
10327
- onEdit: canManage && api.updateCreditAllocation ? () => setEditing(allocation) : void 0,
10328
- onCancel: canManage && api.cancelCreditAllocation ? () => setCanceling(allocation) : void 0
10134
+ member,
10135
+ onEdit: canManage && api.updateCreditPoolMember ? () => setEditing(member) : void 0,
10136
+ onRemove: canManage && api.removeCreditPoolMember ? () => setRemoving(member) : void 0
10329
10137
  },
10330
- allocation._id
10138
+ member._id
10331
10139
  ))
10332
10140
  }
10333
10141
  )
10334
10142
  ] }),
10335
- api.fetchCreditTransfers && !failed && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10336
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { style: { ...cellText2, fontWeight: 500 }, children: "Transfer history" }),
10337
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10338
- MemberListFrame,
10339
- {
10340
- grow: "cap",
10341
- maxHeight: 420,
10342
- contentMinWidth: 680,
10343
- header: tableHeader(TRANSFER_COLUMNS, ["Date", "Type", "Workspace", "Credits"]),
10344
- isEmpty: transfersFailed || transfers.length === 0,
10345
- emptyText: transfersFailed ? "Couldn't load transfers. Try again later." : "No transfers yet",
10346
- children: transfers.map((transfer) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TransferRow, { transfer }, transfer.id))
10347
- }
10348
- ),
10349
- transfers.length < transfersTotal && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10350
- import_ui15.ScButton,
10351
- {
10352
- text: loadingMore ? "Loading\u2026" : "Load more",
10353
- variant: "secondary",
10354
- size: "sm",
10355
- state: loadingMore ? "disabled" : "default",
10356
- onClick: loadingMore ? void 0 : () => void loadMoreTransfers()
10357
- }
10358
- ) })
10359
- ] }),
10360
- creating && api.createCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10361
- CreditAllocationModal,
10143
+ adding && api.addCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10144
+ CreditPoolMemberModal,
10362
10145
  {
10363
- kind: "create",
10364
- targets,
10365
- balance,
10366
- onSave: (input) => api.createCreditAllocation(input),
10367
- onClose: () => setCreating(false),
10146
+ kind: "add",
10147
+ candidates,
10148
+ onSave: (input) => api.addCreditPoolMember(input),
10149
+ onClose: () => setAdding(false),
10368
10150
  onSaved: () => void load({ refresh: true })
10369
10151
  }
10370
10152
  ),
10371
- editing && api.updateCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10372
- CreditAllocationModal,
10153
+ editing && api.updateCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10154
+ CreditPoolMemberModal,
10373
10155
  {
10374
10156
  kind: "edit",
10375
- allocation: editing,
10376
- targetName: allocationWorkspaceName(editing.targetWorkspaceName),
10377
- onSave: (patch) => api.updateCreditAllocation(editing._id, patch),
10157
+ member: editing,
10158
+ memberName: poolWorkspaceName(editing.memberWorkspaceName),
10159
+ onSave: (patch) => api.updateCreditPoolMember(editing._id, patch),
10378
10160
  onClose: () => setEditing(null),
10379
10161
  onSaved: () => void load({ refresh: true })
10380
10162
  }
10381
10163
  ),
10382
- canceling && api.cancelCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10383
- CancelCreditAllocationModal,
10164
+ removing && api.removeCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10165
+ RemovePoolMemberModal,
10384
10166
  {
10385
- allocation: canceling,
10386
- targetName: allocationWorkspaceName(canceling.targetWorkspaceName),
10387
- onCancel: (reason) => api.cancelCreditAllocation(canceling._id, reason),
10388
- onClose: () => setCanceling(null),
10389
- onCanceled: ({ planNotRestored: stuck }) => {
10390
- if (stuck) {
10391
- const id = canceling._id;
10392
- setPlanNotRestored((prev) => new Set(prev).add(id));
10393
- }
10394
- void load({ refresh: true });
10395
- }
10167
+ member: removing,
10168
+ memberName: poolWorkspaceName(removing.memberWorkspaceName),
10169
+ sponsorName: workspaceName,
10170
+ onRemove: (reason) => api.removeCreditPoolMember(removing._id, reason),
10171
+ onClose: () => setRemoving(null),
10172
+ onRemoved: () => void load({ refresh: true })
10396
10173
  }
10397
10174
  )
10398
10175
  ] });
@@ -11282,8 +11059,11 @@ function InvoiceHistoryTable({
11282
11059
  function UsageLogsTable({
11283
11060
  logs,
11284
11061
  userMap,
11285
- loading
11062
+ loading,
11063
+ currentWorkspaceId = null,
11064
+ usedByNames = {}
11286
11065
  }) {
11066
+ const showWorkspace = logs.some((log) => usedByWorkspaceKey(log, currentWorkspaceId));
11287
11067
  if (loading) {
11288
11068
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11289
11069
  "div",
@@ -11360,6 +11140,14 @@ function UsageLogsTable({
11360
11140
  style: { flexShrink: 0, width: "7.5rem" }
11361
11141
  }
11362
11142
  ),
11143
+ showWorkspace && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11144
+ import_ui16.ScHeader,
11145
+ {
11146
+ text: "Workspace",
11147
+ state: "none",
11148
+ style: { flexShrink: 0, width: "9rem" }
11149
+ }
11150
+ ),
11363
11151
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11364
11152
  import_ui16.ScHeader,
11365
11153
  {
@@ -11406,6 +11194,8 @@ function UsageLogsTable({
11406
11194
  logs.map((log) => {
11407
11195
  const userName = log.triggeredByUserId ? userMap[log.triggeredByUserId] || "Unknown" : "System";
11408
11196
  const app = log.service?.serviceGroupName || "\u2014";
11197
+ const usedBy = usedByWorkspaceKey(log, currentWorkspaceId);
11198
+ const workspaceLabel2 = usedBy ? `Used by ${usedByNames[usedBy.toLowerCase()] ?? "another workspace"}` : "This workspace";
11409
11199
  const activity = log.type === "return" ? "Returned Credits" : log.meta?.info || "Credit usage";
11410
11200
  const isPositive = log.type === "return" || log.type === "add";
11411
11201
  const signedAmount = `${isPositive ? "+" : "-"}${log.amount}`;
@@ -11435,6 +11225,18 @@ function UsageLogsTable({
11435
11225
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, flex: 1 }, children: app }),
11436
11226
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, flex: 1 }, children: activity }),
11437
11227
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: userName }),
11228
+ showWorkspace && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11229
+ "div",
11230
+ {
11231
+ style: {
11232
+ ...rowCellRegular,
11233
+ width: "9rem",
11234
+ color: usedBy ? "var(--alias-text---icons-primary, #f5f5f5)" : "var(--alias-text---icons-muted)"
11235
+ },
11236
+ title: workspaceLabel2,
11237
+ children: workspaceLabel2
11238
+ }
11239
+ ),
11438
11240
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: formatDate3(log.createdAt) }),
11439
11241
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11440
11242
  "div",
@@ -11503,8 +11305,8 @@ function BillingContent({
11503
11305
  onShowPlansChange,
11504
11306
  showPerUserLimits: showPerUserLimitsProp,
11505
11307
  onShowPerUserLimitsChange,
11506
- showCreditAllocations: showCreditAllocationsProp,
11507
- onShowCreditAllocationsChange
11308
+ showSharedCredits: showSharedCreditsProp,
11309
+ onShowSharedCreditsChange
11508
11310
  } = {}) {
11509
11311
  const { auth, workspace, organization, api, toast, navigation } = useSettingsAdapters();
11510
11312
  const user = auth.user;
@@ -11529,21 +11331,23 @@ function BillingContent({
11529
11331
  if (onShowPerUserLimitsChange) onShowPerUserLimitsChange(next);
11530
11332
  else setShowPerUserLimitsInternal(next);
11531
11333
  };
11532
- const canAllocateCredits = canShowCreditAllocations(
11334
+ const canShareCredits = canShowSharedCredits(
11533
11335
  api,
11534
11336
  workspace.current,
11535
11337
  organization?.current
11536
11338
  );
11537
- const [showCreditAllocationsInternal, setShowCreditAllocationsInternal] = (0, import_react22.useState)(false);
11538
- const showCreditAllocations = (showCreditAllocationsProp ?? showCreditAllocationsInternal) && canAllocateCredits;
11539
- const setShowCreditAllocations = (next) => {
11540
- if (onShowCreditAllocationsChange) onShowCreditAllocationsChange(next);
11541
- else setShowCreditAllocationsInternal(next);
11339
+ const [showSharedCreditsInternal, setShowSharedCreditsInternal] = (0, import_react22.useState)(false);
11340
+ const showSharedCredits = (showSharedCreditsProp ?? showSharedCreditsInternal) && canShareCredits;
11341
+ const setShowSharedCredits = (next) => {
11342
+ if (onShowSharedCreditsChange) onShowSharedCreditsChange(next);
11343
+ else setShowSharedCreditsInternal(next);
11542
11344
  };
11543
11345
  const [showExportModal, setShowExportModal] = (0, import_react22.useState)(false);
11544
11346
  const [showBuyCreditsModal, setShowBuyCreditsModal] = (0, import_react22.useState)(false);
11545
11347
  const [subscription, setSubscription] = (0, import_react22.useState)(null);
11546
11348
  const [availableCredits, setAvailableCredits] = (0, import_react22.useState)(null);
11349
+ const [sharedPool, setSharedPool] = (0, import_react22.useState)(null);
11350
+ const [usedByNames, setUsedByNames] = (0, import_react22.useState)({});
11547
11351
  const [products, setProducts] = (0, import_react22.useState)([]);
11548
11352
  const [invoices, setInvoices] = (0, import_react22.useState)([]);
11549
11353
  const [totalCredits, setTotalCredits] = (0, import_react22.useState)(null);
@@ -11594,6 +11398,7 @@ function BillingContent({
11594
11398
  if (subRes?.data) setSubscription(subRes.data);
11595
11399
  if (creditsRes?.availableCredits != null)
11596
11400
  setAvailableCredits(creditsRes.availableCredits);
11401
+ setSharedPool(creditsRes?.sharedPool ?? null);
11597
11402
  if (productsRes?.data) {
11598
11403
  const normalizedWorkspaceCountry = workspace.current?.country?.trim().toUpperCase();
11599
11404
  const targetCurrency = normalizedWorkspaceCountry === "IN" ? "inr" : "usd";
@@ -11642,7 +11447,12 @@ function BillingContent({
11642
11447
  );
11643
11448
  } else if (api.fetchUsageLogs) {
11644
11449
  const res = await api.fetchUsageLogs();
11645
- setUsageLogs(res.data?.length ? res.data : []);
11450
+ const logs = res.data?.length ? res.data : [];
11451
+ setUsageLogs(logs);
11452
+ if (api.fetchCreditPool && logs.some((log) => usedByWorkspaceKey(log, workspaceId))) {
11453
+ const pool = await api.fetchCreditPool().catch(() => null);
11454
+ setUsedByNames(usedByNamesFrom(pool));
11455
+ }
11646
11456
  } else {
11647
11457
  setUsageLogs([]);
11648
11458
  }
@@ -11653,7 +11463,7 @@ function BillingContent({
11653
11463
  setLoadingTable(false);
11654
11464
  }
11655
11465
  },
11656
- [api]
11466
+ [api, workspaceId]
11657
11467
  );
11658
11468
  const handlePlanAction = (0, import_react22.useCallback)(
11659
11469
  async ({ productId, priceId, planName: planName2 }) => {
@@ -11811,10 +11621,13 @@ function BillingContent({
11811
11621
  );
11812
11622
  const scheduledDowngradeText = subscription?.downgradeScheduled && subscription.plan?.name ? `Downgrades to ${subscription.plan.name}${scheduledDowngradeDateLabel ? ` ${scheduledDowngradeDateLabel}` : ""}` : void 0;
11813
11623
  const isPlanDetailsLoading = !billingOverviewLoaded || loadingBilling || subscription?.plan == null || !planName || planName === "-" || !planAmount || currentPlanCredits == null;
11814
- const usedCredits = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
11815
- const progressPct = usedCredits != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(usedCredits / totalCredits * 100)) : 0;
11816
- const remainingPct = 100 - progressPct;
11817
- const usageText = usedCredits != null ? formatCredits2(usedCredits) : availableCredits != null ? `${formatCredits2(availableCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11624
+ const {
11625
+ used: usedCredits,
11626
+ remaining: remainingCredits,
11627
+ progressPct,
11628
+ remainingPct
11629
+ } = creditsMeter({ availableCredits, totalCredits, sharedPool });
11630
+ const usageText = usedCredits != null ? formatCredits2(usedCredits) : remainingCredits != null ? `${formatCredits2(remainingCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11818
11631
  const handleExport = async (startDate, endDate) => {
11819
11632
  const end = new Date(endDate);
11820
11633
  end.setHours(23, 59, 59, 999);
@@ -11891,7 +11704,7 @@ function BillingContent({
11891
11704
  function fmtFilename(d) {
11892
11705
  return d.toISOString().slice(0, 10);
11893
11706
  }
11894
- async function handleBuyCredits(credits, price) {
11707
+ async function handleBuyCredits(credits2, price) {
11895
11708
  if (!workspaceId) {
11896
11709
  toast.error("Missing workspace details for credit top-up");
11897
11710
  return;
@@ -11903,14 +11716,14 @@ function BillingContent({
11903
11716
  "Buy credits confirmed",
11904
11717
  {
11905
11718
  action: "confirm_buy_credits",
11906
- additionalInfo: `${credits}:${price}`
11719
+ additionalInfo: `${credits2}:${price}`
11907
11720
  }
11908
11721
  );
11909
11722
  try {
11910
11723
  const topupCurrency = workspace.current?.country?.trim().toUpperCase() === "IN" ? "inr" : "usd";
11911
11724
  const res = await api.buyCredits({
11912
11725
  serviceName: DEFAULT_TOPUP_SERVICE,
11913
- credits,
11726
+ credits: credits2,
11914
11727
  unitAmount: price,
11915
11728
  itemCount: 1,
11916
11729
  currency: topupCurrency,
@@ -11928,7 +11741,7 @@ function BillingContent({
11928
11741
  setAvailableCredits(payloadData.availableCredits);
11929
11742
  } else {
11930
11743
  setAvailableCredits(
11931
- (prev) => typeof prev === "number" ? prev + credits : prev
11744
+ (prev) => typeof prev === "number" ? prev + credits2 : prev
11932
11745
  );
11933
11746
  }
11934
11747
  toast.success("Top-up created");
@@ -11998,13 +11811,13 @@ function BillingContent({
11998
11811
  }
11999
11812
  ) });
12000
11813
  }
12001
- if (showCreditAllocations) {
12002
- 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: [
12003
- !onShowCreditAllocationsChange && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(BackToBilling, { onClick: () => setShowCreditAllocations(false) }),
12004
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(CreditAllocationsView, {})
11814
+ if (showSharedCredits) {
11815
+ 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: [
11816
+ !onShowSharedCreditsChange && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(BackToBilling, { onClick: () => setShowSharedCredits(false) }),
11817
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SharedCreditsView, {})
12005
11818
  ] });
12006
- if (embedded) return allocationsView;
12007
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: allocationsView });
11819
+ if (embedded) return sharedCreditsView;
11820
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: sharedCreditsView });
12008
11821
  }
12009
11822
  if (showPerUserLimits) {
12010
11823
  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: [
@@ -12071,7 +11884,7 @@ function BillingContent({
12071
11884
  {
12072
11885
  usageLabel: "Credits used",
12073
11886
  usageText,
12074
- remainingText: availableCredits != null ? `${formatCredits2(availableCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
11887
+ remainingText: remainingCredits != null ? `${formatCredits2(remainingCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
12075
11888
  buyButtonText: "Buy credits",
12076
11889
  buyButtonState: isFreePlan ? "disabled" : "default",
12077
11890
  buyButtonHoverText: isFreePlan ? "Pro plan required" : void 0,
@@ -12091,6 +11904,50 @@ function BillingContent({
12091
11904
  ]
12092
11905
  }
12093
11906
  ),
11907
+ sharedPool ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
11908
+ "div",
11909
+ {
11910
+ role: "status",
11911
+ className: "flex flex-col shrink-0 w-full",
11912
+ style: {
11913
+ gap: 4,
11914
+ padding: 16,
11915
+ backgroundColor: "var(--alias-surface-base)",
11916
+ border: "1px solid var(--alias-border-subtle)",
11917
+ borderRadius: "var(--radius-3xl)"
11918
+ },
11919
+ children: [
11920
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11921
+ "span",
11922
+ {
11923
+ style: {
11924
+ fontFamily: "var(--font-family-font-family-body)",
11925
+ fontSize: "var(--font-size-font-size-sm)",
11926
+ fontWeight: 500,
11927
+ lineHeight: "var(--line-height-line-height-sm)",
11928
+ color: "var(--alias-text---icons-primary)"
11929
+ },
11930
+ children: sharedPoolBannerText(sharedPool)
11931
+ }
11932
+ ),
11933
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
11934
+ "span",
11935
+ {
11936
+ style: {
11937
+ fontFamily: "var(--font-family-font-family-body)",
11938
+ fontSize: "var(--font-size-font-size-sm)",
11939
+ lineHeight: "var(--line-height-line-height-sm)",
11940
+ color: "var(--alias-text---icons-muted)"
11941
+ },
11942
+ children: [
11943
+ pausedOwnCreditsText(sharedPool),
11944
+ sharedPool.cycleEnd ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : ""
11945
+ ]
11946
+ }
11947
+ )
11948
+ ]
11949
+ }
11950
+ ) : null,
12094
11951
  usage ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12095
11952
  "div",
12096
11953
  {
@@ -12164,7 +12021,7 @@ function BillingContent({
12164
12021
  ]
12165
12022
  }
12166
12023
  ) : null,
12167
- canAllocateCredits ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12024
+ canShareCredits ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12168
12025
  "div",
12169
12026
  {
12170
12027
  className: "flex items-center justify-between shrink-0 w-full",
@@ -12187,7 +12044,7 @@ function BillingContent({
12187
12044
  lineHeight: "var(--line-height-line-height-sm)",
12188
12045
  color: "var(--alias-text---icons-primary)"
12189
12046
  },
12190
- children: "Credit allocations"
12047
+ children: "Shared credits"
12191
12048
  }
12192
12049
  ),
12193
12050
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12199,7 +12056,7 @@ function BillingContent({
12199
12056
  lineHeight: "var(--line-height-line-height-sm)",
12200
12057
  color: "var(--alias-text---icons-muted)"
12201
12058
  },
12202
- children: "Share this workspace's credits with other workspaces in your organization"
12059
+ children: "Let other workspaces in your organization spend this workspace's credits"
12203
12060
  }
12204
12061
  )
12205
12062
  ] }),
@@ -12209,7 +12066,7 @@ function BillingContent({
12209
12066
  text: "Manage",
12210
12067
  variant: "secondary",
12211
12068
  size: "md",
12212
- onClick: () => setShowCreditAllocations(true)
12069
+ onClick: () => setShowSharedCredits(true)
12213
12070
  }
12214
12071
  )
12215
12072
  ]
@@ -12259,7 +12116,9 @@ function BillingContent({
12259
12116
  {
12260
12117
  logs: usageLogs,
12261
12118
  userMap,
12262
- loading: loadingTable
12119
+ loading: loadingTable,
12120
+ currentWorkspaceId: workspaceId,
12121
+ usedByNames
12263
12122
  }
12264
12123
  ),
12265
12124
  showExportModal && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12278,8 +12137,8 @@ function BillingContent({
12278
12137
  workspace.current?.country
12279
12138
  ),
12280
12139
  onClose: () => setShowBuyCreditsModal(false),
12281
- onBuy: (credits, price) => {
12282
- handleBuyCredits(credits, price);
12140
+ onBuy: (credits2, price) => {
12141
+ handleBuyCredits(credits2, price);
12283
12142
  }
12284
12143
  }
12285
12144
  )
@@ -14927,20 +14786,20 @@ function SettingsContentInner({
14927
14786
  const isBillingPlans = activeTab === "billing" && isWorkspaceAdmin && billingShowPlans;
14928
14787
  const [billingShowPerUserLimits, setBillingShowPerUserLimits] = (0, import_react26.useState)(false);
14929
14788
  const isBillingPerUserLimits = activeTab === "billing" && isWorkspaceAdmin && billingShowPerUserLimits && !billingShowPlans;
14930
- const [billingShowCreditAllocations, setBillingShowCreditAllocations] = (0, import_react26.useState)(false);
14931
- const isBillingCreditAllocations = activeTab === "billing" && isWorkspaceAdmin && billingShowCreditAllocations && !billingShowPlans && !billingShowPerUserLimits;
14932
- const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingCreditAllocations;
14789
+ const [billingShowSharedCredits, setBillingShowSharedCredits] = (0, import_react26.useState)(false);
14790
+ const isBillingSharedCredits = activeTab === "billing" && isWorkspaceAdmin && billingShowSharedCredits && !billingShowPlans && !billingShowPerUserLimits;
14791
+ const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingSharedCredits;
14933
14792
  (0, import_react26.useEffect)(() => {
14934
14793
  if (activeTab !== "billing" || !isWorkspaceAdmin) {
14935
14794
  setBillingShowPlans(false);
14936
14795
  setBillingShowPerUserLimits(false);
14937
- setBillingShowCreditAllocations(false);
14796
+ setBillingShowSharedCredits(false);
14938
14797
  }
14939
14798
  }, [activeTab, isWorkspaceAdmin]);
14940
14799
  (0, import_react26.useEffect)(() => {
14941
14800
  setBillingShowPlans(false);
14942
14801
  setBillingShowPerUserLimits(false);
14943
- setBillingShowCreditAllocations(false);
14802
+ setBillingShowSharedCredits(false);
14944
14803
  }, [workspaceId]);
14945
14804
  (0, import_react26.useEffect)(() => {
14946
14805
  if (!onClose) return;
@@ -14955,15 +14814,15 @@ function SettingsContentInner({
14955
14814
  setBillingShowPerUserLimits(false);
14956
14815
  return;
14957
14816
  }
14958
- if (billingShowCreditAllocations) {
14959
- setBillingShowCreditAllocations(false);
14817
+ if (billingShowSharedCredits) {
14818
+ setBillingShowSharedCredits(false);
14960
14819
  return;
14961
14820
  }
14962
14821
  onClose();
14963
14822
  };
14964
14823
  document.addEventListener("keydown", handleKeyDown);
14965
14824
  return () => document.removeEventListener("keydown", handleKeyDown);
14966
- }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowCreditAllocations]);
14825
+ }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowSharedCredits]);
14967
14826
  const [localWorkspaces, setLocalWorkspaces] = (0, import_react26.useState)(null);
14968
14827
  const [wsModalOpen, setWsModalOpen] = (0, import_react26.useState)(false);
14969
14828
  const [wsModalMode, setWsModalMode] = (0, import_react26.useState)("create");
@@ -15284,7 +15143,7 @@ function SettingsContentInner({
15284
15143
  if (isBillingSubView) {
15285
15144
  setBillingShowPlans(false);
15286
15145
  setBillingShowPerUserLimits(false);
15287
- setBillingShowCreditAllocations(false);
15146
+ setBillingShowSharedCredits(false);
15288
15147
  return;
15289
15148
  }
15290
15149
  if (onBack) onBack();
@@ -15387,7 +15246,7 @@ function SettingsContentInner({
15387
15246
  }
15388
15247
  )
15389
15248
  ] }),
15390
- isBillingCreditAllocations && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
15249
+ isBillingSharedCredits && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
15391
15250
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_icons17.SiconRight, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15392
15251
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
15393
15252
  "span",
@@ -15399,7 +15258,7 @@ function SettingsContentInner({
15399
15258
  lineHeight: "var(--line-height-line-height-xl)",
15400
15259
  color: "var(--alias-text---icons-primary)"
15401
15260
  },
15402
- children: "Credit allocations"
15261
+ children: "Shared credits"
15403
15262
  }
15404
15263
  )
15405
15264
  ] })
@@ -15612,7 +15471,7 @@ function SettingsContentInner({
15612
15471
  setBillingShowPlans(open);
15613
15472
  if (open) {
15614
15473
  setBillingShowPerUserLimits(false);
15615
- setBillingShowCreditAllocations(false);
15474
+ setBillingShowSharedCredits(false);
15616
15475
  }
15617
15476
  },
15618
15477
  showPerUserLimits: billingShowPerUserLimits,
@@ -15620,12 +15479,12 @@ function SettingsContentInner({
15620
15479
  setBillingShowPerUserLimits(open);
15621
15480
  if (open) {
15622
15481
  setBillingShowPlans(false);
15623
- setBillingShowCreditAllocations(false);
15482
+ setBillingShowSharedCredits(false);
15624
15483
  }
15625
15484
  },
15626
- showCreditAllocations: billingShowCreditAllocations,
15627
- onShowCreditAllocationsChange: (open) => {
15628
- setBillingShowCreditAllocations(open);
15485
+ showSharedCredits: billingShowSharedCredits,
15486
+ onShowSharedCreditsChange: (open) => {
15487
+ setBillingShowSharedCredits(open);
15629
15488
  if (open) {
15630
15489
  setBillingShowPlans(false);
15631
15490
  setBillingShowPerUserLimits(false);