@streamoid/settings 0.10.1 → 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
 
@@ -8032,14 +8031,14 @@ function BuyCreditsModal({
8032
8031
  const maxCredits = Math.round(
8033
8032
  topupConfig.maxAmount / topupConfig.pricePerCredit
8034
8033
  );
8035
- const [credits, setCredits] = (0, import_react15.useState)(minCredits);
8034
+ const [credits2, setCredits] = (0, import_react15.useState)(minCredits);
8036
8035
  const [creditsInput, setCreditsInput] = (0, import_react15.useState)(String(minCredits));
8037
8036
  const [priceInput, setPriceInput] = (0, import_react15.useState)(
8038
8037
  String(minCredits * topupConfig.pricePerCredit)
8039
8038
  );
8040
8039
  const [activeField, setActiveField] = (0, import_react15.useState)("credits");
8041
- const parsedCredits = creditsInput.length ? Number(creditsInput) : credits;
8042
- const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits;
8040
+ const parsedCredits = creditsInput.length ? Number(creditsInput) : credits2;
8041
+ const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits2;
8043
8042
  const pricedCredits = previewCredits > 0 ? previewCredits : 0;
8044
8043
  const price = Number(
8045
8044
  (pricedCredits * topupConfig.pricePerCredit).toFixed(2)
@@ -8061,8 +8060,8 @@ function BuyCreditsModal({
8061
8060
  setCreditsInput(String(derived));
8062
8061
  setPriceInput(String(amount));
8063
8062
  };
8064
- const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits - STEP));
8065
- 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));
8066
8065
  const handleCreditsChange = (value) => {
8067
8066
  const raw = value.replace(/[^\d]/g, "");
8068
8067
  setCreditsInput(raw);
@@ -8196,7 +8195,7 @@ function BuyCreditsModal({
8196
8195
  style: {
8197
8196
  width: 24,
8198
8197
  height: 24,
8199
- opacity: credits <= minCredits ? 0.3 : 1,
8198
+ opacity: credits2 <= minCredits ? 0.3 : 1,
8200
8199
  transition: "opacity 0.15s ease"
8201
8200
  },
8202
8201
  onClick: handleDecrement,
@@ -8254,7 +8253,7 @@ function BuyCreditsModal({
8254
8253
  style: {
8255
8254
  width: 24,
8256
8255
  height: 24,
8257
- opacity: credits >= maxCredits ? 0.3 : 1,
8256
+ opacity: credits2 >= maxCredits ? 0.3 : 1,
8258
8257
  transition: "opacity 0.15s ease"
8259
8258
  },
8260
8259
  onClick: handleIncrement,
@@ -8386,7 +8385,7 @@ function BuyCreditsModal({
8386
8385
  pointerEvents: isInvalid ? "none" : "auto",
8387
8386
  transition: "opacity 0.2s ease"
8388
8387
  },
8389
- onClick: () => onBuy(credits, price),
8388
+ onClick: () => onBuy(credits2, price),
8390
8389
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
8391
8390
  "span",
8392
8391
  {
@@ -9126,7 +9125,7 @@ function PerUserLimitsView() {
9126
9125
  ] });
9127
9126
  }
9128
9127
 
9129
- // src/credit-allocations-view.tsx
9128
+ // src/shared-credits-view.tsx
9130
9129
  var import_react21 = require("react");
9131
9130
  var import_ui15 = require("@streamoid/ui");
9132
9131
 
@@ -9375,23 +9374,23 @@ function ConfirmActionModal({
9375
9374
  );
9376
9375
  }
9377
9376
 
9378
- // src/credit-allocation-modal.tsx
9377
+ // src/credit-pool-modal.tsx
9379
9378
  var import_react19 = require("react");
9380
9379
  var import_ui13 = require("@streamoid/ui");
9381
9380
  var import_icons13 = require("@streamoid/icons");
9382
9381
 
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);
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);
9388
9387
  }
9389
- function cleanAllocationValueInput(raw) {
9388
+ function cleanPoolValueInput(raw) {
9390
9389
  const kept = raw.replace(/[^\d.]/g, "");
9391
9390
  const dot = kept.indexOf(".");
9392
9391
  return dot === -1 ? kept : kept.slice(0, dot + 1) + kept.slice(dot + 1).replace(/\./g, "");
9393
9392
  }
9394
- function validateAllocationValue(mode, raw, balance) {
9393
+ function validatePoolValue(mode, raw) {
9395
9394
  const trimmed = raw.trim();
9396
9395
  if (!trimmed) return { ok: false, reason: mode === "percent" ? "Enter a percentage" : "Enter a number of credits" };
9397
9396
  if (!/^\d+(\.\d{1,2})?$/.test(trimmed)) {
@@ -9400,64 +9399,61 @@ function validateAllocationValue(mode, raw, balance) {
9400
9399
  const value = Number(trimmed);
9401
9400
  if (!(value > 0)) return { ok: false, reason: "Enter a number greater than 0" };
9402
9401
  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
9402
  return { ok: true, value };
9410
9403
  }
9411
- function validateAllocationText(raw, max) {
9404
+ function validatePoolText(raw, max) {
9412
9405
  const value = raw.trim();
9413
9406
  if (!value) return { ok: false, reason: "Required" };
9414
9407
  if (value.length > max) return { ok: false, reason: `At most ${max} characters` };
9415
9408
  return { ok: true, value };
9416
9409
  }
9417
- function formatAllocationSplit(split) {
9410
+ var credits = (value) => `${formatCredits(value)} ${value === 1 ? "credit" : "credits"}`;
9411
+ function formatPoolSplit(split) {
9418
9412
  if (split.mode === "percent") return `${formatCredits(split.value)}%`;
9419
- return `${formatCredits(split.value)} ${split.value === 1 ? "credit" : "credits"}`;
9413
+ return credits(split.value);
9420
9414
  }
9421
- function formatAllocationRepeat(recurring) {
9422
- 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;
9423
9418
  }
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) {
9419
+ function poolWorkspaceName(name) {
9430
9420
  return name?.trim() || "Unknown workspace";
9431
9421
  }
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);
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);
9461
9457
  if (!error) return null;
9462
9458
  const status = error.status;
9463
9459
  const message = [status?.message, error.error, error.message].find(
@@ -9466,29 +9462,18 @@ function findAllocationError(failure) {
9466
9462
  const details = error.details && typeof error.details === "object" ? error.details : {};
9467
9463
  return { code: error.code, details, message: message ?? null };
9468
9464
  }
9469
- var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
9470
- function allocationErrorMessage(failure) {
9471
- const found = findAllocationError(failure);
9465
+ function poolErrorMessage(failure) {
9466
+ const found = findPoolError(failure);
9472
9467
  if (!found) return null;
9473
9468
  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":
9469
+ case "SPONSOR_ON_FREE_PLAN":
9483
9470
  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.";
9471
+ case "POOL_NOT_ALLOWED":
9472
+ return found.message ? found.message.replace(/\.?$/, ".") : "Credits can't be shared with that workspace.";
9488
9473
  }
9489
9474
  }
9490
9475
 
9491
- // src/credit-allocation-modal.tsx
9476
+ // src/credit-pool-modal.tsx
9492
9477
  var import_jsx_runtime25 = require("react/jsx-runtime");
9493
9478
  var MODAL_T = {
9494
9479
  primary: "var(--alias-text---icons-primary)",
@@ -9521,7 +9506,7 @@ var modalBodyText = {
9521
9506
  fontSize: T16.fsSm,
9522
9507
  lineHeight: T16.lhSm
9523
9508
  };
9524
- function AllocationModalShell({
9509
+ function PoolModalShell({
9525
9510
  title,
9526
9511
  busy,
9527
9512
  onClose,
@@ -9622,7 +9607,7 @@ function AllocationModalShell({
9622
9607
  }
9623
9608
  );
9624
9609
  }
9625
- function AllocationWarning({ children }) {
9610
+ function PoolWarning({ children }) {
9626
9611
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9627
9612
  "div",
9628
9613
  {
@@ -9641,68 +9626,55 @@ function AllocationWarning({ children }) {
9641
9626
  );
9642
9627
  }
9643
9628
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9644
- function CreditAllocationModal(props) {
9629
+ function CreditPoolMemberModal(props) {
9645
9630
  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");
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");
9649
9634
  const [raw, setRaw] = (0, import_react19.useState)(editing ? String(editing.value) : "");
9650
- const [recurring, setRecurring] = (0, import_react19.useState)(editing?.recurring ?? true);
9651
9635
  const [note, setNote] = (0, import_react19.useState)("");
9652
9636
  const [touched, setTouched] = (0, import_react19.useState)(false);
9653
9637
  const [saving, setSaving] = (0, import_react19.useState)(false);
9654
9638
  const [serverError, setServerError] = (0, import_react19.useState)(null);
9655
9639
  (0, import_react19.useEffect)(() => {
9656
9640
  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);
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);
9664
9646
  const canSave = !saving && changed && valid;
9665
9647
  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
9648
  const handleSave = async () => {
9668
9649
  if (!canSave || !value.ok) return;
9669
9650
  setSaving(true);
9670
9651
  try {
9671
- if (props.kind === "create") {
9672
- const created = await props.onSave({
9673
- targetWorkspaceId: targetId,
9652
+ if (props.kind === "add") {
9653
+ const added = await props.onSave({
9654
+ memberWorkspaceId: memberId,
9674
9655
  mode,
9675
9656
  value: value.value,
9676
- recurring,
9677
9657
  note: noteCheck.ok ? noteCheck.value : ""
9678
9658
  });
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
- }
9659
+ toast.success(`Credits shared. ${describeAllowance(added)}.`);
9685
9660
  } else {
9686
- await props.onSave(
9687
- splitChanged ? { mode, value: value.value, recurring } : { recurring }
9688
- );
9689
- 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)}.`);
9690
9663
  }
9691
9664
  props.onSaved();
9692
9665
  props.onClose();
9693
9666
  } catch (err) {
9694
- const message = allocationErrorMessage(err);
9667
+ const message = poolErrorMessage(err);
9695
9668
  if (message) setServerError(message);
9696
- else toast.error(errorMessage(err, { method: props.kind === "create" ? "POST" : "PATCH" }));
9697
- if (findAllocationError(err)?.details.allocationId) props.onSaved();
9669
+ else toast.error(errorMessage(err, { method: props.kind === "add" ? "POST" : "PATCH" }));
9698
9670
  setSaving(false);
9699
9671
  }
9700
9672
  };
9701
- 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 })) : [];
9702
9674
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
9703
- AllocationModalShell,
9675
+ PoolModalShell,
9704
9676
  {
9705
- title: props.kind === "create" ? "New credit allocation" : "Edit credit allocation",
9677
+ title: props.kind === "add" ? "Share credits" : "Edit shared credits",
9706
9678
  busy: saving,
9707
9679
  onClose: props.onClose,
9708
9680
  footer: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
@@ -9719,7 +9691,7 @@ function CreditAllocationModal(props) {
9719
9691
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9720
9692
  import_ui13.ScButton,
9721
9693
  {
9722
- text: props.kind === "create" ? "Create allocation" : "Save",
9694
+ text: props.kind === "add" ? "Share credits" : "Save",
9723
9695
  variant: "primary",
9724
9696
  size: "md",
9725
9697
  state: canSave ? "default" : "disabled",
@@ -9729,29 +9701,29 @@ function CreditAllocationModal(props) {
9729
9701
  ] }),
9730
9702
  children: [
9731
9703
  /* @__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)(
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)(
9734
9706
  import_ui13.ScSelect,
9735
9707
  {
9736
- value: targetId,
9737
- onChange: setTargetId,
9738
- options: targetOptions,
9708
+ value: memberId,
9709
+ onChange: setMemberId,
9710
+ options: candidateOptions,
9739
9711
  placeholder: "Choose a workspace",
9740
9712
  disabled: saving
9741
9713
  }
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 })
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 })
9743
9715
  ] }),
9744
9716
  /* @__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" }),
9717
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "How much, each billing cycle" }),
9746
9718
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex items-center", style: { gap: T16.sp3xl }, children: [
9747
9719
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9748
9720
  import_ui13.ScPairtext,
9749
9721
  {
9750
9722
  type: "radio",
9751
- content: "Fixed credits",
9752
- checked: mode === "fixed",
9723
+ content: "Percentage",
9724
+ checked: mode === "percent",
9753
9725
  onChange: () => {
9754
- if (!saving) setMode("fixed");
9726
+ if (!saving) setMode("percent");
9755
9727
  }
9756
9728
  }
9757
9729
  ),
@@ -9759,10 +9731,10 @@ function CreditAllocationModal(props) {
9759
9731
  import_ui13.ScPairtext,
9760
9732
  {
9761
9733
  type: "radio",
9762
- content: "Percentage",
9763
- checked: mode === "percent",
9734
+ content: "Fixed credits",
9735
+ checked: mode === "fixed",
9764
9736
  onChange: () => {
9765
- if (!saving) setMode("percent");
9737
+ if (!saving) setMode("fixed");
9766
9738
  }
9767
9739
  }
9768
9740
  )
@@ -9790,7 +9762,7 @@ function CreditAllocationModal(props) {
9790
9762
  value: raw,
9791
9763
  disabled: saving,
9792
9764
  onChange: (e) => {
9793
- setRaw(cleanAllocationValueInput(e.target.value));
9765
+ setRaw(cleanPoolValueInput(e.target.value));
9794
9766
  setTouched(true);
9795
9767
  },
9796
9768
  className: "flex-1 min-w-0",
@@ -9808,39 +9780,18 @@ function CreditAllocationModal(props) {
9808
9780
  ]
9809
9781
  }
9810
9782
  ),
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
- ) })
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
+ ] })
9836
9787
  ] }),
9837
- 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: [
9838
9789
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: fieldLabel, children: "Note" }),
9839
9790
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
9840
9791
  import_ui13.ScTextArea,
9841
9792
  {
9842
9793
  value: note,
9843
- maxLength: ALLOCATION_NOTE_MAX_LENGTH,
9794
+ maxLength: POOL_NOTE_MAX_LENGTH,
9844
9795
  placeholder: "Why are these credits being shared?",
9845
9796
  disabled: saving,
9846
9797
  onChange: (e) => setNote(e.target.value),
@@ -9852,117 +9803,57 @@ function CreditAllocationModal(props) {
9852
9803
  "Required. ",
9853
9804
  note.trim().length,
9854
9805
  "/",
9855
- ALLOCATION_NOTE_MAX_LENGTH
9806
+ POOL_NOTE_MAX_LENGTH
9856
9807
  ] })
9857
9808
  ] }),
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." }),
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." }),
9859
9810
  serverError && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
9860
9811
  ]
9861
9812
  }
9862
9813
  );
9863
9814
  }
9864
9815
 
9865
- // src/cancel-credit-allocation-modal.tsx
9816
+ // src/remove-pool-member-modal.tsx
9866
9817
  var import_react20 = require("react");
9867
9818
  var import_ui14 = require("@streamoid/ui");
9868
9819
  var import_jsx_runtime26 = require("react/jsx-runtime");
9869
- function CancelCreditAllocationModal({
9870
- allocation,
9871
- targetName,
9872
- onCancel,
9820
+ function RemovePoolMemberModal({
9821
+ member,
9822
+ memberName,
9823
+ sponsorName,
9824
+ onRemove,
9873
9825
  onClose,
9874
- onCanceled
9826
+ onRemoved
9875
9827
  }) {
9876
9828
  const { toast } = useSettingsAdapters();
9877
9829
  const [reason, setReason] = (0, import_react20.useState)("");
9878
9830
  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);
9831
+ const check = validatePoolText(reason, REMOVE_REASON_MAX_LENGTH);
9881
9832
  const canConfirm = !saving && check.ok;
9882
9833
  const handleConfirm = async () => {
9883
9834
  if (!canConfirm || !check.ok) return;
9884
9835
  setSaving(true);
9885
9836
  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));
9837
+ await onRemove(check.value);
9838
+ toast.success(`${memberName} can no longer spend ${sponsorName}'s credits`);
9839
+ onRemoved();
9898
9840
  onClose();
9899
9841
  } catch (err) {
9900
9842
  toast.error(errorMessage(err, { method: "DELETE" }));
9901
9843
  setSaving(false);
9902
9844
  }
9903
9845
  };
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
9846
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
9956
- AllocationModalShell,
9847
+ PoolModalShell,
9957
9848
  {
9958
- title: "Cancel credit allocation",
9849
+ title: "Stop sharing credits",
9959
9850
  busy: saving,
9960
9851
  onClose,
9961
9852
  footer: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
9962
9853
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9963
9854
  import_ui14.ScButton,
9964
9855
  {
9965
- text: "Keep allocation",
9856
+ text: "Keep sharing",
9966
9857
  variant: "secondary",
9967
9858
  size: "md",
9968
9859
  state: saving ? "disabled" : "default",
@@ -9972,7 +9863,7 @@ function CancelCreditAllocationModal({
9972
9863
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9973
9864
  import_ui14.ScButton,
9974
9865
  {
9975
- text: "Cancel allocation",
9866
+ text: "Stop sharing",
9976
9867
  variant: "error",
9977
9868
  size: "md",
9978
9869
  state: canConfirm ? "default" : "disabled",
@@ -9982,16 +9873,18 @@ function CancelCreditAllocationModal({
9982
9873
  ] }),
9983
9874
  children: [
9984
9875
  /* @__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
- "?"
9876
+ "This stops ",
9877
+ memberName,
9878
+ " spending ",
9879
+ sponsorName,
9880
+ "'s credits immediately."
9990
9881
  ] }),
9991
9882
  /* @__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." : ""
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."
9995
9888
  ] }),
9996
9889
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9997
9890
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Reason" }),
@@ -9999,8 +9892,8 @@ function CancelCreditAllocationModal({
9999
9892
  import_ui14.ScTextArea,
10000
9893
  {
10001
9894
  value: reason,
10002
- maxLength: CANCEL_REASON_MAX_LENGTH,
10003
- placeholder: "Why is this allocation ending?",
9895
+ maxLength: REMOVE_REASON_MAX_LENGTH,
9896
+ placeholder: "Why is this workspace no longer sharing these credits?",
10004
9897
  disabled: saving,
10005
9898
  onChange: (e) => setReason(e.target.value),
10006
9899
  rows: 3,
@@ -10014,16 +9907,13 @@ function CancelCreditAllocationModal({
10014
9907
  );
10015
9908
  }
10016
9909
 
10017
- // src/credit-allocations-view.tsx
9910
+ // src/shared-credits-view.tsx
10018
9911
  var import_jsx_runtime27 = require("react/jsx-runtime");
10019
9912
  var T17 = {
10020
9913
  primary: "var(--alias-text---icons-primary)",
10021
9914
  muted: "var(--alias-text---icons-muted)",
10022
9915
  tertiary: "var(--alias-text---icons-tertiary)",
10023
9916
  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
9917
  fillPlus: "var(--alias-fill-neutral-neutralplus)",
10028
9918
  surfaceBase: "var(--alias-surface-base)",
10029
9919
  borderSubtle: "var(--alias-border-subtle)",
@@ -10037,9 +9927,7 @@ var T17 = {
10037
9927
  r3xl: "var(--radius-3xl)",
10038
9928
  rFull: "var(--radius-full)"
10039
9929
  };
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;
9930
+ var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
10043
9931
  var cellText2 = {
10044
9932
  fontFamily: T17.font,
10045
9933
  fontSize: T17.fsSm,
@@ -10061,138 +9949,67 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ (0, import_jsx_runtime27.
10061
9949
  children: labels.map((label) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: label }, label))
10062
9950
  }
10063
9951
  );
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,
9952
+ function MemberRow2({
9953
+ member,
10086
9954
  onEdit,
10087
- onCancel
9955
+ onRemove
10088
9956
  }) {
10089
- const failed = initialTransferWarning(allocation);
10090
- const sent = initialTransferSummary(allocation);
10091
- const active = allocation.status === "active";
9957
+ const spent = member.remaining <= 0;
10092
9958
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10093
9959
  "div",
10094
9960
  {
10095
9961
  style: {
10096
9962
  borderBottom: `1px solid ${T17.borderSubtle}`,
10097
9963
  display: "grid",
10098
- gridTemplateColumns: GIVING_COLUMNS,
9964
+ gridTemplateColumns: MEMBER_COLUMNS2,
10099
9965
  alignItems: "center",
10100
9966
  padding: `10px ${T17.sp3xl}`,
10101
9967
  gap: 24
10102
9968
  },
10103
9969
  children: [
10104
9970
  /* @__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" })
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 })
10117
9973
  ] }),
10118
- /* @__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) }),
10119
9977
  /* @__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 })
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 })
10122
9980
  ] })
10123
9981
  ]
10124
9982
  }
10125
9983
  );
10126
9984
  }
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() {
9985
+ function SharedCreditsView() {
10157
9986
  const { api, workspace, toast } = useSettingsAdapters();
10158
9987
  const workspaceId = workspace.current?.id ?? null;
9988
+ const workspaceName = workspace.current?.name?.trim() || "this workspace";
10159
9989
  const [data, setData] = (0, import_react21.useState)(null);
10160
9990
  const [loading, setLoading] = (0, import_react21.useState)(true);
10161
9991
  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);
9992
+ const [candidates, setCandidates] = (0, import_react21.useState)([]);
9993
+ const [adding, setAdding] = (0, import_react21.useState)(false);
10169
9994
  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());
9995
+ const [removing, setRemoving] = (0, import_react21.useState)(null);
10172
9996
  const latestLoad = (0, import_react21.useRef)(0);
10173
9997
  const load = (0, import_react21.useCallback)(
10174
9998
  async ({ refresh = false } = {}) => {
10175
9999
  const loadId = ++latestLoad.current;
10176
- if (!api.fetchCreditAllocations) {
10000
+ if (!api.fetchCreditPool) {
10177
10001
  setLoading(false);
10178
10002
  setFailed(true);
10179
10003
  return;
10180
10004
  }
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
- ]);
10005
+ const next = await api.fetchCreditPool().catch(() => null);
10185
10006
  if (loadId !== latestLoad.current) return;
10186
10007
  if (refresh && !next) {
10187
- 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.");
10188
10009
  return;
10189
10010
  }
10190
10011
  setFailed(!next);
10191
10012
  setData(next);
10192
- setTransfersFailed(Boolean(api.fetchCreditTransfers) && !page);
10193
- setTransfers(page?.transfers ?? []);
10194
- setTransfersTotal(page?.total ?? 0);
10195
- setTransfersPage(page ? 1 : 0);
10196
10013
  setLoading(false);
10197
10014
  },
10198
10015
  [api, toast]
@@ -10201,11 +10018,9 @@ function CreditAllocationsView() {
10201
10018
  setLoading(true);
10202
10019
  setFailed(false);
10203
10020
  setData(null);
10204
- setTransfers([]);
10205
- setCreating(false);
10021
+ setAdding(false);
10206
10022
  setEditing(null);
10207
- setCanceling(null);
10208
- setPlanNotRestored(/* @__PURE__ */ new Set());
10023
+ setRemoving(null);
10209
10024
  void load();
10210
10025
  }, [workspaceId]);
10211
10026
  (0, import_react21.useEffect)(
@@ -10215,36 +10030,21 @@ function CreditAllocationsView() {
10215
10030
  []
10216
10031
  );
10217
10032
  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);
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);
10223
10039
  return;
10224
10040
  }
10225
10041
  try {
10226
- setTargets(await api.fetchAllocationTargets());
10227
- setCreating(true);
10042
+ setCandidates(await api.fetchCreditPoolCandidates());
10043
+ setAdding(true);
10228
10044
  } catch {
10229
10045
  toast.error("Couldn't load this organization's workspaces. Try again.");
10230
10046
  }
10231
10047
  };
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
10048
  if (loading) {
10249
10049
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10250
10050
  "div",
@@ -10267,9 +10067,7 @@ function CreditAllocationsView() {
10267
10067
  }
10268
10068
  );
10269
10069
  }
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;
10070
+ const members = data?.members ?? [];
10273
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: [
10274
10072
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-start justify-between shrink-0 w-full", style: { gap: 12 }, children: [
10275
10073
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex flex-col min-w-0", style: { gap: 4 }, children: [
@@ -10277,12 +10075,12 @@ function CreditAllocationsView() {
10277
10075
  "span",
10278
10076
  {
10279
10077
  style: { fontFamily: T17.font, fontSize: T17.fsXl, fontWeight: 500, lineHeight: T17.lhXl, color: T17.primary },
10280
- children: "Credit allocations"
10078
+ children: "Shared credits"
10281
10079
  }
10282
10080
  ),
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." })
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." })
10284
10082
  ] }),
10285
- 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() })
10286
10084
  ] }),
10287
10085
  sponsor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10288
10086
  "div",
@@ -10296,103 +10094,74 @@ function CreditAllocationsView() {
10296
10094
  borderRadius: T17.r3xl
10297
10095
  },
10298
10096
  children: [
10299
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, fontWeight: 500 }, children: [
10300
- "Funded by ",
10301
- allocationWorkspaceName(sponsor.sourceWorkspaceName)
10302
- ] }),
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
+ }) }),
10303
10102
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { style: { ...cellText2, color: T17.muted }, children: [
10304
- "Receives ",
10305
- 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)}` : "",
10306
10107
  "."
10307
10108
  ] })
10308
10109
  ]
10309
10110
  }
10310
10111
  ),
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" }),
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" }),
10313
10114
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10314
10115
  MemberListFrame,
10315
10116
  {
10316
10117
  grow: "cap",
10317
10118
  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,
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,
10324
10125
  {
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
10126
+ member,
10127
+ onEdit: canManage && api.updateCreditPoolMember ? () => setEditing(member) : void 0,
10128
+ onRemove: canManage && api.removeCreditPoolMember ? () => setRemoving(member) : void 0
10329
10129
  },
10330
- allocation._id
10130
+ member._id
10331
10131
  ))
10332
10132
  }
10333
10133
  )
10334
10134
  ] }),
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,
10135
+ adding && api.addCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10136
+ CreditPoolMemberModal,
10362
10137
  {
10363
- kind: "create",
10364
- targets,
10365
- balance,
10366
- onSave: (input) => api.createCreditAllocation(input),
10367
- onClose: () => setCreating(false),
10138
+ kind: "add",
10139
+ candidates,
10140
+ onSave: (input) => api.addCreditPoolMember(input),
10141
+ onClose: () => setAdding(false),
10368
10142
  onSaved: () => void load({ refresh: true })
10369
10143
  }
10370
10144
  ),
10371
- editing && api.updateCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10372
- CreditAllocationModal,
10145
+ editing && api.updateCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10146
+ CreditPoolMemberModal,
10373
10147
  {
10374
10148
  kind: "edit",
10375
- allocation: editing,
10376
- targetName: allocationWorkspaceName(editing.targetWorkspaceName),
10377
- onSave: (patch) => api.updateCreditAllocation(editing._id, patch),
10149
+ member: editing,
10150
+ memberName: poolWorkspaceName(editing.memberWorkspaceName),
10151
+ onSave: (patch) => api.updateCreditPoolMember(editing._id, patch),
10378
10152
  onClose: () => setEditing(null),
10379
10153
  onSaved: () => void load({ refresh: true })
10380
10154
  }
10381
10155
  ),
10382
- canceling && api.cancelCreditAllocation && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10383
- CancelCreditAllocationModal,
10156
+ removing && api.removeCreditPoolMember && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10157
+ RemovePoolMemberModal,
10384
10158
  {
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
- }
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 })
10396
10165
  }
10397
10166
  )
10398
10167
  ] });
@@ -11282,8 +11051,11 @@ function InvoiceHistoryTable({
11282
11051
  function UsageLogsTable({
11283
11052
  logs,
11284
11053
  userMap,
11285
- loading
11054
+ loading,
11055
+ currentWorkspaceId = null,
11056
+ usedByNames = {}
11286
11057
  }) {
11058
+ const showWorkspace = logs.some((log) => usedByWorkspaceKey(log, currentWorkspaceId));
11287
11059
  if (loading) {
11288
11060
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11289
11061
  "div",
@@ -11360,6 +11132,14 @@ function UsageLogsTable({
11360
11132
  style: { flexShrink: 0, width: "7.5rem" }
11361
11133
  }
11362
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
+ ),
11363
11143
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11364
11144
  import_ui16.ScHeader,
11365
11145
  {
@@ -11406,6 +11186,8 @@ function UsageLogsTable({
11406
11186
  logs.map((log) => {
11407
11187
  const userName = log.triggeredByUserId ? userMap[log.triggeredByUserId] || "Unknown" : "System";
11408
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";
11409
11191
  const activity = log.type === "return" ? "Returned Credits" : log.meta?.info || "Credit usage";
11410
11192
  const isPositive = log.type === "return" || log.type === "add";
11411
11193
  const signedAmount = `${isPositive ? "+" : "-"}${log.amount}`;
@@ -11435,6 +11217,18 @@ function UsageLogsTable({
11435
11217
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, flex: 1 }, children: app }),
11436
11218
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, flex: 1 }, children: activity }),
11437
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
+ ),
11438
11232
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: formatDate3(log.createdAt) }),
11439
11233
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11440
11234
  "div",
@@ -11503,8 +11297,8 @@ function BillingContent({
11503
11297
  onShowPlansChange,
11504
11298
  showPerUserLimits: showPerUserLimitsProp,
11505
11299
  onShowPerUserLimitsChange,
11506
- showCreditAllocations: showCreditAllocationsProp,
11507
- onShowCreditAllocationsChange
11300
+ showSharedCredits: showSharedCreditsProp,
11301
+ onShowSharedCreditsChange
11508
11302
  } = {}) {
11509
11303
  const { auth, workspace, organization, api, toast, navigation } = useSettingsAdapters();
11510
11304
  const user = auth.user;
@@ -11529,21 +11323,23 @@ function BillingContent({
11529
11323
  if (onShowPerUserLimitsChange) onShowPerUserLimitsChange(next);
11530
11324
  else setShowPerUserLimitsInternal(next);
11531
11325
  };
11532
- const canAllocateCredits = canShowCreditAllocations(
11326
+ const canShareCredits = canShowSharedCredits(
11533
11327
  api,
11534
11328
  workspace.current,
11535
11329
  organization?.current
11536
11330
  );
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);
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);
11542
11336
  };
11543
11337
  const [showExportModal, setShowExportModal] = (0, import_react22.useState)(false);
11544
11338
  const [showBuyCreditsModal, setShowBuyCreditsModal] = (0, import_react22.useState)(false);
11545
11339
  const [subscription, setSubscription] = (0, import_react22.useState)(null);
11546
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)({});
11547
11343
  const [products, setProducts] = (0, import_react22.useState)([]);
11548
11344
  const [invoices, setInvoices] = (0, import_react22.useState)([]);
11549
11345
  const [totalCredits, setTotalCredits] = (0, import_react22.useState)(null);
@@ -11594,6 +11390,7 @@ function BillingContent({
11594
11390
  if (subRes?.data) setSubscription(subRes.data);
11595
11391
  if (creditsRes?.availableCredits != null)
11596
11392
  setAvailableCredits(creditsRes.availableCredits);
11393
+ setSharedPool(creditsRes?.sharedPool ?? null);
11597
11394
  if (productsRes?.data) {
11598
11395
  const normalizedWorkspaceCountry = workspace.current?.country?.trim().toUpperCase();
11599
11396
  const targetCurrency = normalizedWorkspaceCountry === "IN" ? "inr" : "usd";
@@ -11642,7 +11439,12 @@ function BillingContent({
11642
11439
  );
11643
11440
  } else if (api.fetchUsageLogs) {
11644
11441
  const res = await api.fetchUsageLogs();
11645
- 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
+ }
11646
11448
  } else {
11647
11449
  setUsageLogs([]);
11648
11450
  }
@@ -11653,7 +11455,7 @@ function BillingContent({
11653
11455
  setLoadingTable(false);
11654
11456
  }
11655
11457
  },
11656
- [api]
11458
+ [api, workspaceId]
11657
11459
  );
11658
11460
  const handlePlanAction = (0, import_react22.useCallback)(
11659
11461
  async ({ productId, priceId, planName: planName2 }) => {
@@ -11811,10 +11613,13 @@ function BillingContent({
11811
11613
  );
11812
11614
  const scheduledDowngradeText = subscription?.downgradeScheduled && subscription.plan?.name ? `Downgrades to ${subscription.plan.name}${scheduledDowngradeDateLabel ? ` ${scheduledDowngradeDateLabel}` : ""}` : void 0;
11813
11615
  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";
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";
11818
11623
  const handleExport = async (startDate, endDate) => {
11819
11624
  const end = new Date(endDate);
11820
11625
  end.setHours(23, 59, 59, 999);
@@ -11891,7 +11696,7 @@ function BillingContent({
11891
11696
  function fmtFilename(d) {
11892
11697
  return d.toISOString().slice(0, 10);
11893
11698
  }
11894
- async function handleBuyCredits(credits, price) {
11699
+ async function handleBuyCredits(credits2, price) {
11895
11700
  if (!workspaceId) {
11896
11701
  toast.error("Missing workspace details for credit top-up");
11897
11702
  return;
@@ -11903,14 +11708,14 @@ function BillingContent({
11903
11708
  "Buy credits confirmed",
11904
11709
  {
11905
11710
  action: "confirm_buy_credits",
11906
- additionalInfo: `${credits}:${price}`
11711
+ additionalInfo: `${credits2}:${price}`
11907
11712
  }
11908
11713
  );
11909
11714
  try {
11910
11715
  const topupCurrency = workspace.current?.country?.trim().toUpperCase() === "IN" ? "inr" : "usd";
11911
11716
  const res = await api.buyCredits({
11912
11717
  serviceName: DEFAULT_TOPUP_SERVICE,
11913
- credits,
11718
+ credits: credits2,
11914
11719
  unitAmount: price,
11915
11720
  itemCount: 1,
11916
11721
  currency: topupCurrency,
@@ -11928,7 +11733,7 @@ function BillingContent({
11928
11733
  setAvailableCredits(payloadData.availableCredits);
11929
11734
  } else {
11930
11735
  setAvailableCredits(
11931
- (prev) => typeof prev === "number" ? prev + credits : prev
11736
+ (prev) => typeof prev === "number" ? prev + credits2 : prev
11932
11737
  );
11933
11738
  }
11934
11739
  toast.success("Top-up created");
@@ -11998,13 +11803,13 @@ function BillingContent({
11998
11803
  }
11999
11804
  ) });
12000
11805
  }
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, {})
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, {})
12005
11810
  ] });
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 });
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 });
12008
11813
  }
12009
11814
  if (showPerUserLimits) {
12010
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: [
@@ -12071,7 +11876,7 @@ function BillingContent({
12071
11876
  {
12072
11877
  usageLabel: "Credits used",
12073
11878
  usageText,
12074
- remainingText: availableCredits != null ? `${formatCredits2(availableCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
11879
+ remainingText: remainingCredits != null ? `${formatCredits2(remainingCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
12075
11880
  buyButtonText: "Buy credits",
12076
11881
  buyButtonState: isFreePlan ? "disabled" : "default",
12077
11882
  buyButtonHoverText: isFreePlan ? "Pro plan required" : void 0,
@@ -12091,6 +11896,50 @@ function BillingContent({
12091
11896
  ]
12092
11897
  }
12093
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,
12094
11943
  usage ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12095
11944
  "div",
12096
11945
  {
@@ -12164,7 +12013,7 @@ function BillingContent({
12164
12013
  ]
12165
12014
  }
12166
12015
  ) : null,
12167
- canAllocateCredits ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12016
+ canShareCredits ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
12168
12017
  "div",
12169
12018
  {
12170
12019
  className: "flex items-center justify-between shrink-0 w-full",
@@ -12187,7 +12036,7 @@ function BillingContent({
12187
12036
  lineHeight: "var(--line-height-line-height-sm)",
12188
12037
  color: "var(--alias-text---icons-primary)"
12189
12038
  },
12190
- children: "Credit allocations"
12039
+ children: "Shared credits"
12191
12040
  }
12192
12041
  ),
12193
12042
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12199,7 +12048,7 @@ function BillingContent({
12199
12048
  lineHeight: "var(--line-height-line-height-sm)",
12200
12049
  color: "var(--alias-text---icons-muted)"
12201
12050
  },
12202
- 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"
12203
12052
  }
12204
12053
  )
12205
12054
  ] }),
@@ -12209,7 +12058,7 @@ function BillingContent({
12209
12058
  text: "Manage",
12210
12059
  variant: "secondary",
12211
12060
  size: "md",
12212
- onClick: () => setShowCreditAllocations(true)
12061
+ onClick: () => setShowSharedCredits(true)
12213
12062
  }
12214
12063
  )
12215
12064
  ]
@@ -12259,7 +12108,9 @@ function BillingContent({
12259
12108
  {
12260
12109
  logs: usageLogs,
12261
12110
  userMap,
12262
- loading: loadingTable
12111
+ loading: loadingTable,
12112
+ currentWorkspaceId: workspaceId,
12113
+ usedByNames
12263
12114
  }
12264
12115
  ),
12265
12116
  showExportModal && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
@@ -12278,8 +12129,8 @@ function BillingContent({
12278
12129
  workspace.current?.country
12279
12130
  ),
12280
12131
  onClose: () => setShowBuyCreditsModal(false),
12281
- onBuy: (credits, price) => {
12282
- handleBuyCredits(credits, price);
12132
+ onBuy: (credits2, price) => {
12133
+ handleBuyCredits(credits2, price);
12283
12134
  }
12284
12135
  }
12285
12136
  )
@@ -14927,20 +14778,20 @@ function SettingsContentInner({
14927
14778
  const isBillingPlans = activeTab === "billing" && isWorkspaceAdmin && billingShowPlans;
14928
14779
  const [billingShowPerUserLimits, setBillingShowPerUserLimits] = (0, import_react26.useState)(false);
14929
14780
  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;
14781
+ const [billingShowSharedCredits, setBillingShowSharedCredits] = (0, import_react26.useState)(false);
14782
+ const isBillingSharedCredits = activeTab === "billing" && isWorkspaceAdmin && billingShowSharedCredits && !billingShowPlans && !billingShowPerUserLimits;
14783
+ const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingSharedCredits;
14933
14784
  (0, import_react26.useEffect)(() => {
14934
14785
  if (activeTab !== "billing" || !isWorkspaceAdmin) {
14935
14786
  setBillingShowPlans(false);
14936
14787
  setBillingShowPerUserLimits(false);
14937
- setBillingShowCreditAllocations(false);
14788
+ setBillingShowSharedCredits(false);
14938
14789
  }
14939
14790
  }, [activeTab, isWorkspaceAdmin]);
14940
14791
  (0, import_react26.useEffect)(() => {
14941
14792
  setBillingShowPlans(false);
14942
14793
  setBillingShowPerUserLimits(false);
14943
- setBillingShowCreditAllocations(false);
14794
+ setBillingShowSharedCredits(false);
14944
14795
  }, [workspaceId]);
14945
14796
  (0, import_react26.useEffect)(() => {
14946
14797
  if (!onClose) return;
@@ -14955,15 +14806,15 @@ function SettingsContentInner({
14955
14806
  setBillingShowPerUserLimits(false);
14956
14807
  return;
14957
14808
  }
14958
- if (billingShowCreditAllocations) {
14959
- setBillingShowCreditAllocations(false);
14809
+ if (billingShowSharedCredits) {
14810
+ setBillingShowSharedCredits(false);
14960
14811
  return;
14961
14812
  }
14962
14813
  onClose();
14963
14814
  };
14964
14815
  document.addEventListener("keydown", handleKeyDown);
14965
14816
  return () => document.removeEventListener("keydown", handleKeyDown);
14966
- }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowCreditAllocations]);
14817
+ }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowSharedCredits]);
14967
14818
  const [localWorkspaces, setLocalWorkspaces] = (0, import_react26.useState)(null);
14968
14819
  const [wsModalOpen, setWsModalOpen] = (0, import_react26.useState)(false);
14969
14820
  const [wsModalMode, setWsModalMode] = (0, import_react26.useState)("create");
@@ -15284,7 +15135,7 @@ function SettingsContentInner({
15284
15135
  if (isBillingSubView) {
15285
15136
  setBillingShowPlans(false);
15286
15137
  setBillingShowPerUserLimits(false);
15287
- setBillingShowCreditAllocations(false);
15138
+ setBillingShowSharedCredits(false);
15288
15139
  return;
15289
15140
  }
15290
15141
  if (onBack) onBack();
@@ -15387,7 +15238,7 @@ function SettingsContentInner({
15387
15238
  }
15388
15239
  )
15389
15240
  ] }),
15390
- 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: [
15391
15242
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_icons17.SiconRight, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15392
15243
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
15393
15244
  "span",
@@ -15399,7 +15250,7 @@ function SettingsContentInner({
15399
15250
  lineHeight: "var(--line-height-line-height-xl)",
15400
15251
  color: "var(--alias-text---icons-primary)"
15401
15252
  },
15402
- children: "Credit allocations"
15253
+ children: "Shared credits"
15403
15254
  }
15404
15255
  )
15405
15256
  ] })
@@ -15612,7 +15463,7 @@ function SettingsContentInner({
15612
15463
  setBillingShowPlans(open);
15613
15464
  if (open) {
15614
15465
  setBillingShowPerUserLimits(false);
15615
- setBillingShowCreditAllocations(false);
15466
+ setBillingShowSharedCredits(false);
15616
15467
  }
15617
15468
  },
15618
15469
  showPerUserLimits: billingShowPerUserLimits,
@@ -15620,12 +15471,12 @@ function SettingsContentInner({
15620
15471
  setBillingShowPerUserLimits(open);
15621
15472
  if (open) {
15622
15473
  setBillingShowPlans(false);
15623
- setBillingShowCreditAllocations(false);
15474
+ setBillingShowSharedCredits(false);
15624
15475
  }
15625
15476
  },
15626
- showCreditAllocations: billingShowCreditAllocations,
15627
- onShowCreditAllocationsChange: (open) => {
15628
- setBillingShowCreditAllocations(open);
15477
+ showSharedCredits: billingShowSharedCredits,
15478
+ onShowSharedCreditsChange: (open) => {
15479
+ setBillingShowSharedCredits(open);
15629
15480
  if (open) {
15630
15481
  setBillingShowPlans(false);
15631
15482
  setBillingShowPerUserLimits(false);