@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.mjs CHANGED
@@ -7987,14 +7987,14 @@ function BuyCreditsModal({
7987
7987
  const maxCredits = Math.round(
7988
7988
  topupConfig.maxAmount / topupConfig.pricePerCredit
7989
7989
  );
7990
- const [credits, setCredits] = useState13(minCredits);
7990
+ const [credits2, setCredits] = useState13(minCredits);
7991
7991
  const [creditsInput, setCreditsInput] = useState13(String(minCredits));
7992
7992
  const [priceInput, setPriceInput] = useState13(
7993
7993
  String(minCredits * topupConfig.pricePerCredit)
7994
7994
  );
7995
7995
  const [activeField, setActiveField] = useState13("credits");
7996
- const parsedCredits = creditsInput.length ? Number(creditsInput) : credits;
7997
- const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits;
7996
+ const parsedCredits = creditsInput.length ? Number(creditsInput) : credits2;
7997
+ const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits2;
7998
7998
  const pricedCredits = previewCredits > 0 ? previewCredits : 0;
7999
7999
  const price = Number(
8000
8000
  (pricedCredits * topupConfig.pricePerCredit).toFixed(2)
@@ -8016,8 +8016,8 @@ function BuyCreditsModal({
8016
8016
  setCreditsInput(String(derived));
8017
8017
  setPriceInput(String(amount));
8018
8018
  };
8019
- const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits - STEP));
8020
- const handleIncrement = () => syncFromCredits(Math.min(maxCredits, credits + STEP));
8019
+ const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits2 - STEP));
8020
+ const handleIncrement = () => syncFromCredits(Math.min(maxCredits, credits2 + STEP));
8021
8021
  const handleCreditsChange = (value) => {
8022
8022
  const raw = value.replace(/[^\d]/g, "");
8023
8023
  setCreditsInput(raw);
@@ -8151,7 +8151,7 @@ function BuyCreditsModal({
8151
8151
  style: {
8152
8152
  width: 24,
8153
8153
  height: 24,
8154
- opacity: credits <= minCredits ? 0.3 : 1,
8154
+ opacity: credits2 <= minCredits ? 0.3 : 1,
8155
8155
  transition: "opacity 0.15s ease"
8156
8156
  },
8157
8157
  onClick: handleDecrement,
@@ -8209,7 +8209,7 @@ function BuyCreditsModal({
8209
8209
  style: {
8210
8210
  width: 24,
8211
8211
  height: 24,
8212
- opacity: credits >= maxCredits ? 0.3 : 1,
8212
+ opacity: credits2 >= maxCredits ? 0.3 : 1,
8213
8213
  transition: "opacity 0.15s ease"
8214
8214
  },
8215
8215
  onClick: handleIncrement,
@@ -8341,7 +8341,7 @@ function BuyCreditsModal({
8341
8341
  pointerEvents: isInvalid ? "none" : "auto",
8342
8342
  transition: "opacity 0.2s ease"
8343
8343
  },
8344
- onClick: () => onBuy(credits, price),
8344
+ onClick: () => onBuy(credits2, price),
8345
8345
  children: /* @__PURE__ */ jsx21(
8346
8346
  "span",
8347
8347
  {
@@ -9081,7 +9081,7 @@ function PerUserLimitsView() {
9081
9081
  ] });
9082
9082
  }
9083
9083
 
9084
- // src/credit-allocations-view.tsx
9084
+ // src/shared-credits-view.tsx
9085
9085
  import { useCallback as useCallback7, useEffect as useEffect16, useRef as useRef8, useState as useState19 } from "react";
9086
9086
  import { ScButton as ScButton12 } from "@streamoid/ui";
9087
9087
 
@@ -9330,23 +9330,23 @@ function ConfirmActionModal({
9330
9330
  );
9331
9331
  }
9332
9332
 
9333
- // src/credit-allocation-modal.tsx
9333
+ // src/credit-pool-modal.tsx
9334
9334
  import { useEffect as useEffect15, useState as useState17 } from "react";
9335
- import { ScButton as ScButton10, ScPairtext as ScPairtext2, ScSelect, ScTextArea, ScToggleSwitch } from "@streamoid/ui";
9335
+ import { ScButton as ScButton10, ScPairtext as ScPairtext2, ScSelect, ScTextArea } from "@streamoid/ui";
9336
9336
  import { SiconClose as SiconClose8 } from "@streamoid/icons";
9337
9337
 
9338
- // src/credit-allocations.ts
9339
- var ALLOCATION_NOTE_MAX_LENGTH = 500;
9340
- var CANCEL_REASON_MAX_LENGTH = 500;
9341
- function canShowCreditAllocations(api, workspace, organization) {
9342
- return Boolean(api.fetchCreditAllocations) && Boolean(workspace?.organizationId || organization?.id);
9338
+ // src/credit-pool.ts
9339
+ var POOL_NOTE_MAX_LENGTH = 500;
9340
+ var REMOVE_REASON_MAX_LENGTH = 500;
9341
+ function canShowSharedCredits(api, workspace, organization) {
9342
+ return Boolean(api.fetchCreditPool) && Boolean(workspace?.organizationId || organization?.id);
9343
9343
  }
9344
- function cleanAllocationValueInput(raw) {
9344
+ function cleanPoolValueInput(raw) {
9345
9345
  const kept = raw.replace(/[^\d.]/g, "");
9346
9346
  const dot = kept.indexOf(".");
9347
9347
  return dot === -1 ? kept : kept.slice(0, dot + 1) + kept.slice(dot + 1).replace(/\./g, "");
9348
9348
  }
9349
- function validateAllocationValue(mode, raw, balance) {
9349
+ function validatePoolValue(mode, raw) {
9350
9350
  const trimmed = raw.trim();
9351
9351
  if (!trimmed) return { ok: false, reason: mode === "percent" ? "Enter a percentage" : "Enter a number of credits" };
9352
9352
  if (!/^\d+(\.\d{1,2})?$/.test(trimmed)) {
@@ -9355,64 +9355,68 @@ function validateAllocationValue(mode, raw, balance) {
9355
9355
  const value = Number(trimmed);
9356
9356
  if (!(value > 0)) return { ok: false, reason: "Enter a number greater than 0" };
9357
9357
  if (mode === "percent" && value > 100) return { ok: false, reason: "A percentage can be at most 100" };
9358
- if (mode === "fixed" && typeof balance === "number" && Number.isFinite(balance) && value > balance) {
9359
- return {
9360
- ok: false,
9361
- reason: `This workspace has ${formatCredits(balance)} credits available`
9362
- };
9363
- }
9364
9358
  return { ok: true, value };
9365
9359
  }
9366
- function validateAllocationText(raw, max) {
9360
+ function validatePoolText(raw, max) {
9367
9361
  const value = raw.trim();
9368
9362
  if (!value) return { ok: false, reason: "Required" };
9369
9363
  if (value.length > max) return { ok: false, reason: `At most ${max} characters` };
9370
9364
  return { ok: true, value };
9371
9365
  }
9372
- function formatAllocationSplit(split) {
9366
+ var credits = (value) => `${formatCredits(value)} ${value === 1 ? "credit" : "credits"}`;
9367
+ function formatPoolSplit(split) {
9373
9368
  if (split.mode === "percent") return `${formatCredits(split.value)}%`;
9374
- return `${formatCredits(split.value)} ${split.value === 1 ? "credit" : "credits"}`;
9369
+ return credits(split.value);
9375
9370
  }
9376
- function formatAllocationRepeat(recurring) {
9377
- return recurring ? "Every renewal" : "Once";
9371
+ function describeAllowance(member) {
9372
+ const allowance = `${credits(member.allowance)} this cycle`;
9373
+ return member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 ${allowance}` : allowance;
9378
9374
  }
9379
- function describeAllocation(allocation) {
9380
- const split = formatAllocationSplit(allocation);
9381
- if (!allocation.recurring) return `${split} once`;
9382
- return allocation.mode === "percent" ? `${split} of each renewal` : `${split} every renewal`;
9383
- }
9384
- function allocationWorkspaceName(name) {
9375
+ function poolWorkspaceName(name) {
9385
9376
  return name?.trim() || "Unknown workspace";
9386
9377
  }
9387
- function initialTransferWarning(allocation) {
9388
- const transfer = allocation.initialTransfer;
9389
- if (transfer?.status !== "failed") return null;
9390
- const why = transfer.error?.trim();
9391
- return `The first transfer failed${why ? `: ${why}` : ""}. The allocation is active and renewals will still split, but these credits did not move.`;
9392
- }
9393
- function initialTransferSummary(allocation) {
9394
- const transfer = allocation.initialTransfer;
9395
- if (transfer?.status !== "done") return null;
9396
- return `Sent ${formatCredits(transfer.credits)} ${transfer.credits === 1 ? "credit" : "credits"} at setup`;
9378
+ function sharedPoolBannerText(pool) {
9379
+ const sponsor = pool.sponsorName?.trim() || "another workspace";
9380
+ return `Credits shared by ${sponsor}: ${formatCredits(pool.remaining)} of ${formatCredits(pool.allowance)} left this cycle`;
9397
9381
  }
9398
- function cancelOutcomeWarning(result, targetName) {
9399
- const stuck = !result.targetMovedToFree && !result.reason?.trim() && result.allocation?.targetPlanChanged === true;
9400
- if (!result.error && !stuck) return null;
9401
- 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.`;
9382
+ function pausedOwnCreditsText(pool) {
9383
+ const own = pool.ownCredits;
9384
+ if (typeof own !== "number" || !Number.isFinite(own)) {
9385
+ return "This workspace spends these shared credits, not its own; credits bought here can't be used while it does.";
9386
+ }
9387
+ 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.`;
9402
9388
  }
9403
- function cancelOutcomeSummary(result, targetName) {
9404
- const why = result.reason?.trim();
9405
- if (result.targetMovedToFree) return `Allocation canceled. ${targetName} is back on the free plan.`;
9406
- return why ? `Allocation canceled. ${why.replace(/\.?$/, ".")}` : "Allocation canceled";
9389
+ function creditsMeter({
9390
+ availableCredits,
9391
+ totalCredits,
9392
+ sharedPool
9393
+ }) {
9394
+ if (sharedPool) {
9395
+ const used2 = Math.max(0, sharedPool.used);
9396
+ const remaining = Math.max(0, sharedPool.remaining);
9397
+ const progressPct2 = sharedPool.allowance > 0 ? Math.min(100, Math.round(used2 / sharedPool.allowance * 100)) : 100;
9398
+ return { used: used2, remaining, progressPct: progressPct2, remainingPct: 100 - progressPct2 };
9399
+ }
9400
+ const used = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
9401
+ const progressPct = used != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(used / totalCredits * 100)) : 0;
9402
+ return { used, remaining: availableCredits, progressPct, remainingPct: 100 - progressPct };
9403
+ }
9404
+ function usedByWorkspaceKey(log, currentWorkspaceId) {
9405
+ const usedBy = log.meta?.usedBy;
9406
+ if (typeof usedBy !== "string" || !usedBy) return null;
9407
+ return usedBy.toLowerCase() === String(currentWorkspaceId ?? "").toLowerCase() ? null : usedBy;
9408
+ }
9409
+ function usedByNamesFrom(pool) {
9410
+ const names = {};
9411
+ for (const member of [...pool?.formerMembers ?? [], ...pool?.members ?? []]) {
9412
+ const name = member.memberWorkspaceName?.trim();
9413
+ if (name && member.memberWorkspaceKey) names[member.memberWorkspaceKey.toLowerCase()] = name;
9414
+ }
9415
+ return names;
9407
9416
  }
9408
- var ALLOCATION_ERROR_CODES = [
9409
- "INSUFFICIENT_CREDITS",
9410
- "SOURCE_ON_FREE_PLAN",
9411
- "ALLOCATION_NOT_ALLOWED",
9412
- "TARGET_PLAN_CHANGE_FAILED"
9413
- ];
9414
- function findAllocationError(failure) {
9415
- const error = findBillingError(failure, ALLOCATION_ERROR_CODES);
9417
+ var POOL_ERROR_CODES = ["SPONSOR_ON_FREE_PLAN", "POOL_NOT_ALLOWED"];
9418
+ function findPoolError(failure) {
9419
+ const error = findBillingError(failure, POOL_ERROR_CODES);
9416
9420
  if (!error) return null;
9417
9421
  const status = error.status;
9418
9422
  const message = [status?.message, error.error, error.message].find(
@@ -9421,29 +9425,18 @@ function findAllocationError(failure) {
9421
9425
  const details = error.details && typeof error.details === "object" ? error.details : {};
9422
9426
  return { code: error.code, details, message: message ?? null };
9423
9427
  }
9424
- var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
9425
- function allocationErrorMessage(failure) {
9426
- const found = findAllocationError(failure);
9428
+ function poolErrorMessage(failure) {
9429
+ const found = findPoolError(failure);
9427
9430
  if (!found) return null;
9428
9431
  switch (found.code) {
9429
- case "INSUFFICIENT_CREDITS": {
9430
- const balance = num(found.details.currentBalance);
9431
- const required = num(found.details.requiredCredits);
9432
- if (balance !== null && required !== null) {
9433
- return `Not enough credits: this would send ${formatCredits(required)} now, but the workspace has ${formatCredits(balance)}.`;
9434
- }
9435
- return "This workspace doesn't have enough credits to send that now.";
9436
- }
9437
- case "SOURCE_ON_FREE_PLAN":
9432
+ case "SPONSOR_ON_FREE_PLAN":
9438
9433
  return "A workspace on the free plan can't share credits. Upgrade this workspace's plan first.";
9439
- case "ALLOCATION_NOT_ALLOWED":
9440
- return found.message ? found.message.replace(/\.?$/, ".") : "This allocation isn't allowed.";
9441
- case "TARGET_PLAN_CHANGE_FAILED":
9442
- 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.";
9434
+ case "POOL_NOT_ALLOWED":
9435
+ return found.message ? found.message.replace(/\.?$/, ".") : "Credits can't be shared with that workspace.";
9443
9436
  }
9444
9437
  }
9445
9438
 
9446
- // src/credit-allocation-modal.tsx
9439
+ // src/credit-pool-modal.tsx
9447
9440
  import { Fragment as Fragment9, jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
9448
9441
  var MODAL_T = {
9449
9442
  primary: "var(--alias-text---icons-primary)",
@@ -9476,7 +9469,7 @@ var modalBodyText = {
9476
9469
  fontSize: T16.fsSm,
9477
9470
  lineHeight: T16.lhSm
9478
9471
  };
9479
- function AllocationModalShell({
9472
+ function PoolModalShell({
9480
9473
  title,
9481
9474
  busy,
9482
9475
  onClose,
@@ -9577,7 +9570,7 @@ function AllocationModalShell({
9577
9570
  }
9578
9571
  );
9579
9572
  }
9580
- function AllocationWarning({ children }) {
9573
+ function PoolWarning({ children }) {
9581
9574
  return /* @__PURE__ */ jsx25(
9582
9575
  "div",
9583
9576
  {
@@ -9596,68 +9589,55 @@ function AllocationWarning({ children }) {
9596
9589
  );
9597
9590
  }
9598
9591
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9599
- function CreditAllocationModal(props) {
9592
+ function CreditPoolMemberModal(props) {
9600
9593
  const { toast } = useSettingsAdapters();
9601
- const editing = props.kind === "edit" ? props.allocation : null;
9602
- const [targetId, setTargetId] = useState17("");
9603
- const [mode, setMode] = useState17(editing?.mode ?? "fixed");
9594
+ const editing = props.kind === "edit" ? props.member : null;
9595
+ const [memberId, setMemberId] = useState17("");
9596
+ const [mode, setMode] = useState17(editing?.mode ?? "percent");
9604
9597
  const [raw, setRaw] = useState17(editing ? String(editing.value) : "");
9605
- const [recurring, setRecurring] = useState17(editing?.recurring ?? true);
9606
9598
  const [note, setNote] = useState17("");
9607
9599
  const [touched, setTouched] = useState17(false);
9608
9600
  const [saving, setSaving] = useState17(false);
9609
9601
  const [serverError, setServerError] = useState17(null);
9610
9602
  useEffect15(() => {
9611
9603
  setServerError(null);
9612
- }, [targetId, mode, raw, recurring, note]);
9613
- const balance = props.kind === "create" ? props.balance : null;
9614
- const value = validateAllocationValue(mode, raw, props.kind === "create" ? balance : null);
9615
- const noteCheck = validateAllocationText(note, ALLOCATION_NOTE_MAX_LENGTH);
9616
- const splitChanged = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9617
- const changed = !editing || splitChanged || recurring !== editing.recurring;
9618
- const valid = value.ok && (props.kind === "edit" || Boolean(targetId) && noteCheck.ok);
9604
+ }, [memberId, mode, raw, note]);
9605
+ const value = validatePoolValue(mode, raw);
9606
+ const noteCheck = validatePoolText(note, POOL_NOTE_MAX_LENGTH);
9607
+ const changed = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9608
+ const valid = value.ok && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9619
9609
  const canSave = !saving && changed && valid;
9620
9610
  const valueHint = touched && !value.ok ? value.reason : null;
9621
- const percentNow = props.kind === "create" && mode === "percent" && value.ok && balance !== null ? Math.floor(roundCredits(balance * value.value / 100) * 100) / 100 : null;
9622
9611
  const handleSave = async () => {
9623
9612
  if (!canSave || !value.ok) return;
9624
9613
  setSaving(true);
9625
9614
  try {
9626
- if (props.kind === "create") {
9627
- const created = await props.onSave({
9628
- targetWorkspaceId: targetId,
9615
+ if (props.kind === "add") {
9616
+ const added = await props.onSave({
9617
+ memberWorkspaceId: memberId,
9629
9618
  mode,
9630
9619
  value: value.value,
9631
- recurring,
9632
9620
  note: noteCheck.ok ? noteCheck.value : ""
9633
9621
  });
9634
- const warning = initialTransferWarning(created);
9635
- if (warning) toast.error(warning);
9636
- else {
9637
- const sent = initialTransferSummary(created);
9638
- toast.success(sent ? `Allocation created. ${sent}.` : "Allocation created");
9639
- }
9622
+ toast.success(`Credits shared. ${describeAllowance(added)}.`);
9640
9623
  } else {
9641
- await props.onSave(
9642
- splitChanged ? { mode, value: value.value, recurring } : { recurring }
9643
- );
9644
- toast.success("Allocation updated. Applies from the next renewal.");
9624
+ const updated = await props.onSave({ mode, value: value.value });
9625
+ toast.success(`Share updated. ${describeAllowance(updated)}.`);
9645
9626
  }
9646
9627
  props.onSaved();
9647
9628
  props.onClose();
9648
9629
  } catch (err) {
9649
- const message = allocationErrorMessage(err);
9630
+ const message = poolErrorMessage(err);
9650
9631
  if (message) setServerError(message);
9651
- else toast.error(errorMessage(err, { method: props.kind === "create" ? "POST" : "PATCH" }));
9652
- if (findAllocationError(err)?.details.allocationId) props.onSaved();
9632
+ else toast.error(errorMessage(err, { method: props.kind === "add" ? "POST" : "PATCH" }));
9653
9633
  setSaving(false);
9654
9634
  }
9655
9635
  };
9656
- const targetOptions = props.kind === "create" ? props.targets.map((target) => ({ id: target.id, label: target.name })) : [];
9636
+ const candidateOptions = props.kind === "add" ? props.candidates.map((candidate) => ({ id: candidate.id, label: candidate.name })) : [];
9657
9637
  return /* @__PURE__ */ jsxs22(
9658
- AllocationModalShell,
9638
+ PoolModalShell,
9659
9639
  {
9660
- title: props.kind === "create" ? "New credit allocation" : "Edit credit allocation",
9640
+ title: props.kind === "add" ? "Share credits" : "Edit shared credits",
9661
9641
  busy: saving,
9662
9642
  onClose: props.onClose,
9663
9643
  footer: /* @__PURE__ */ jsxs22(Fragment9, { children: [
@@ -9674,7 +9654,7 @@ function CreditAllocationModal(props) {
9674
9654
  /* @__PURE__ */ jsx25(
9675
9655
  ScButton10,
9676
9656
  {
9677
- text: props.kind === "create" ? "Create allocation" : "Save",
9657
+ text: props.kind === "add" ? "Share credits" : "Save",
9678
9658
  variant: "primary",
9679
9659
  size: "md",
9680
9660
  state: canSave ? "default" : "disabled",
@@ -9684,29 +9664,29 @@ function CreditAllocationModal(props) {
9684
9664
  ] }),
9685
9665
  children: [
9686
9666
  /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9687
- /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Send credits to" }),
9688
- props.kind === "create" ? targetOptions.length ? /* @__PURE__ */ jsx25(
9667
+ /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Share with" }),
9668
+ props.kind === "add" ? candidateOptions.length ? /* @__PURE__ */ jsx25(
9689
9669
  ScSelect,
9690
9670
  {
9691
- value: targetId,
9692
- onChange: setTargetId,
9693
- options: targetOptions,
9671
+ value: memberId,
9672
+ onChange: setMemberId,
9673
+ options: candidateOptions,
9694
9674
  placeholder: "Choose a workspace",
9695
9675
  disabled: saving
9696
9676
  }
9697
- ) : /* @__PURE__ */ jsx25("span", { style: { ...modalBodyText, color: T16.muted }, children: "There are no other workspaces in this organization to send credits to." }) : /* @__PURE__ */ jsx25("span", { style: { ...modalBodyText, color: T16.primary }, children: props.targetName })
9677
+ ) : /* @__PURE__ */ jsx25("span", { style: { ...modalBodyText, color: T16.muted }, children: "There are no other workspaces in this organization to share credits with." }) : /* @__PURE__ */ jsx25("span", { style: { ...modalBodyText, color: T16.primary }, children: props.memberName })
9698
9678
  ] }),
9699
9679
  /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spMd }, children: [
9700
- /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "How much" }),
9680
+ /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "How much, each billing cycle" }),
9701
9681
  /* @__PURE__ */ jsxs22("div", { className: "flex items-center", style: { gap: T16.sp3xl }, children: [
9702
9682
  /* @__PURE__ */ jsx25(
9703
9683
  ScPairtext2,
9704
9684
  {
9705
9685
  type: "radio",
9706
- content: "Fixed credits",
9707
- checked: mode === "fixed",
9686
+ content: "Percentage",
9687
+ checked: mode === "percent",
9708
9688
  onChange: () => {
9709
- if (!saving) setMode("fixed");
9689
+ if (!saving) setMode("percent");
9710
9690
  }
9711
9691
  }
9712
9692
  ),
@@ -9714,10 +9694,10 @@ function CreditAllocationModal(props) {
9714
9694
  ScPairtext2,
9715
9695
  {
9716
9696
  type: "radio",
9717
- content: "Percentage",
9718
- checked: mode === "percent",
9697
+ content: "Fixed credits",
9698
+ checked: mode === "fixed",
9719
9699
  onChange: () => {
9720
- if (!saving) setMode("percent");
9700
+ if (!saving) setMode("fixed");
9721
9701
  }
9722
9702
  }
9723
9703
  )
@@ -9745,7 +9725,7 @@ function CreditAllocationModal(props) {
9745
9725
  value: raw,
9746
9726
  disabled: saving,
9747
9727
  onChange: (e) => {
9748
- setRaw(cleanAllocationValueInput(e.target.value));
9728
+ setRaw(cleanPoolValueInput(e.target.value));
9749
9729
  setTouched(true);
9750
9730
  },
9751
9731
  className: "flex-1 min-w-0",
@@ -9763,39 +9743,18 @@ function CreditAllocationModal(props) {
9763
9743
  ]
9764
9744
  }
9765
9745
  ),
9766
- valueHint ? /* @__PURE__ */ jsx25("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : props.kind === "create" ? /* @__PURE__ */ jsx25("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__ */ jsx25("span", { style: { ...modalBodyText, color: T16.tertiary }, children: "Changes apply from the next renewal; credits already sent stay where they are." })
9767
- ] }),
9768
- /* @__PURE__ */ jsxs22("div", { className: "flex items-start justify-between w-full", style: { gap: T16.sp3xl }, children: [
9769
- /* @__PURE__ */ jsxs22("div", { className: "flex flex-col min-w-0", style: { gap: T16.spXs }, children: [
9770
- /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Repeat on every renewal" }),
9771
- /* @__PURE__ */ jsx25("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." })
9772
- ] }),
9773
- /* @__PURE__ */ jsx25("span", { style: { display: "inline-flex", width: 41, height: 25, alignItems: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx25(
9774
- ScToggleSwitch,
9775
- {
9776
- checked: recurring,
9777
- disabled: saving,
9778
- onToggle: (next) => setRecurring(next),
9779
- role: "switch",
9780
- "aria-checked": recurring,
9781
- "aria-label": "Repeat on every renewal",
9782
- tabIndex: 0,
9783
- onKeyDown: (event) => {
9784
- if ((event.key === " " || event.key === "Enter") && !saving) {
9785
- event.preventDefault();
9786
- setRecurring(!recurring);
9787
- }
9788
- }
9789
- }
9790
- ) })
9746
+ valueHint ? /* @__PURE__ */ jsx25("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : /* @__PURE__ */ jsxs22("span", { style: { ...modalBodyText, color: T16.tertiary }, children: [
9747
+ 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.",
9748
+ editing ? " Applies immediately." : ""
9749
+ ] })
9791
9750
  ] }),
9792
- props.kind === "create" && /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9751
+ props.kind === "add" && /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9793
9752
  /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Note" }),
9794
9753
  /* @__PURE__ */ jsx25(
9795
9754
  ScTextArea,
9796
9755
  {
9797
9756
  value: note,
9798
- maxLength: ALLOCATION_NOTE_MAX_LENGTH,
9757
+ maxLength: POOL_NOTE_MAX_LENGTH,
9799
9758
  placeholder: "Why are these credits being shared?",
9800
9759
  disabled: saving,
9801
9760
  onChange: (e) => setNote(e.target.value),
@@ -9807,117 +9766,57 @@ function CreditAllocationModal(props) {
9807
9766
  "Required. ",
9808
9767
  note.trim().length,
9809
9768
  "/",
9810
- ALLOCATION_NOTE_MAX_LENGTH
9769
+ POOL_NOTE_MAX_LENGTH
9811
9770
  ] })
9812
9771
  ] }),
9813
- props.kind === "create" && /* @__PURE__ */ jsx25(AllocationWarning, { children: "A workspace on the free plan moves to a $0 Custom plan funded by this workspace. Cancelling moves it back to free." }),
9772
+ props.kind === "add" && /* @__PURE__ */ jsx25(PoolWarning, { children: "While it spends shared credits, the other workspace uses only this workspace's credits, not its own." }),
9814
9773
  serverError && /* @__PURE__ */ jsx25("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
9815
9774
  ]
9816
9775
  }
9817
9776
  );
9818
9777
  }
9819
9778
 
9820
- // src/cancel-credit-allocation-modal.tsx
9779
+ // src/remove-pool-member-modal.tsx
9821
9780
  import { useState as useState18 } from "react";
9822
9781
  import { ScButton as ScButton11, ScTextArea as ScTextArea2 } from "@streamoid/ui";
9823
9782
  import { Fragment as Fragment10, jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
9824
- function CancelCreditAllocationModal({
9825
- allocation,
9826
- targetName,
9827
- onCancel,
9783
+ function RemovePoolMemberModal({
9784
+ member,
9785
+ memberName,
9786
+ sponsorName,
9787
+ onRemove,
9828
9788
  onClose,
9829
- onCanceled
9789
+ onRemoved
9830
9790
  }) {
9831
9791
  const { toast } = useSettingsAdapters();
9832
9792
  const [reason, setReason] = useState18("");
9833
9793
  const [saving, setSaving] = useState18(false);
9834
- const [outcome, setOutcome] = useState18(null);
9835
- const check = validateAllocationText(reason, CANCEL_REASON_MAX_LENGTH);
9794
+ const check = validatePoolText(reason, REMOVE_REASON_MAX_LENGTH);
9836
9795
  const canConfirm = !saving && check.ok;
9837
9796
  const handleConfirm = async () => {
9838
9797
  if (!canConfirm || !check.ok) return;
9839
9798
  setSaving(true);
9840
9799
  try {
9841
- const result = await onCancel(check.value);
9842
- const warning = cancelOutcomeWarning(
9843
- { ...result, allocation: result.allocation ?? allocation },
9844
- targetName
9845
- );
9846
- onCanceled({ planNotRestored: Boolean(warning) });
9847
- if (warning) {
9848
- setOutcome({ warning, error: result.error });
9849
- setSaving(false);
9850
- return;
9851
- }
9852
- toast.success(cancelOutcomeSummary(result, targetName));
9800
+ await onRemove(check.value);
9801
+ toast.success(`${memberName} can no longer spend ${sponsorName}'s credits`);
9802
+ onRemoved();
9853
9803
  onClose();
9854
9804
  } catch (err) {
9855
9805
  toast.error(errorMessage(err, { method: "DELETE" }));
9856
9806
  setSaving(false);
9857
9807
  }
9858
9808
  };
9859
- const copyId = () => {
9860
- const id = allocation.targetWorkspaceKey;
9861
- const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
9862
- if (!clipboard) {
9863
- toast.error("Couldn't copy. Select the ID and copy it instead.");
9864
- return;
9865
- }
9866
- clipboard.writeText(id).then(
9867
- () => toast.success("Workspace ID copied"),
9868
- () => toast.error("Couldn't copy. Select the ID and copy it instead.")
9869
- );
9870
- };
9871
- if (outcome) {
9872
- return /* @__PURE__ */ jsxs23(
9873
- AllocationModalShell,
9874
- {
9875
- title: "Target plan not restored",
9876
- busy: false,
9877
- onClose,
9878
- footer: /* @__PURE__ */ jsx26(ScButton11, { text: "Got it", variant: "primary", size: "md", onClick: onClose }),
9879
- children: [
9880
- /* @__PURE__ */ jsx26(AllocationWarning, { children: outcome.warning }),
9881
- /* @__PURE__ */ jsxs23("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9882
- /* @__PURE__ */ jsx26("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Workspace ID" }),
9883
- /* @__PURE__ */ jsxs23("div", { className: "flex items-center w-full", style: { gap: MODAL_T.spXl }, children: [
9884
- /* @__PURE__ */ jsx26(
9885
- "code",
9886
- {
9887
- className: "flex-1 min-w-0 truncate",
9888
- style: {
9889
- ...modalBodyText,
9890
- color: MODAL_T.primary,
9891
- background: MODAL_T.fillPlus,
9892
- borderRadius: MODAL_T.r3xl,
9893
- padding: `${MODAL_T.spXl} ${MODAL_T.sp3xl}`,
9894
- userSelect: "all"
9895
- },
9896
- children: allocation.targetWorkspaceKey
9897
- }
9898
- ),
9899
- /* @__PURE__ */ jsx26(ScButton11, { text: "Copy", variant: "secondary", size: "sm", onClick: copyId })
9900
- ] })
9901
- ] }),
9902
- outcome.error && /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: MODAL_T.muted }, children: [
9903
- "Details: ",
9904
- outcome.error
9905
- ] })
9906
- ]
9907
- }
9908
- );
9909
- }
9910
9809
  return /* @__PURE__ */ jsxs23(
9911
- AllocationModalShell,
9810
+ PoolModalShell,
9912
9811
  {
9913
- title: "Cancel credit allocation",
9812
+ title: "Stop sharing credits",
9914
9813
  busy: saving,
9915
9814
  onClose,
9916
9815
  footer: /* @__PURE__ */ jsxs23(Fragment10, { children: [
9917
9816
  /* @__PURE__ */ jsx26(
9918
9817
  ScButton11,
9919
9818
  {
9920
- text: "Keep allocation",
9819
+ text: "Keep sharing",
9921
9820
  variant: "secondary",
9922
9821
  size: "md",
9923
9822
  state: saving ? "disabled" : "default",
@@ -9927,7 +9826,7 @@ function CancelCreditAllocationModal({
9927
9826
  /* @__PURE__ */ jsx26(
9928
9827
  ScButton11,
9929
9828
  {
9930
- text: "Cancel allocation",
9829
+ text: "Stop sharing",
9931
9830
  variant: "error",
9932
9831
  size: "md",
9933
9832
  state: canConfirm ? "default" : "disabled",
@@ -9937,16 +9836,18 @@ function CancelCreditAllocationModal({
9937
9836
  ] }),
9938
9837
  children: [
9939
9838
  /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: MODAL_T.primary }, children: [
9940
- "Stop sending ",
9941
- describeAllocation(allocation),
9942
- " to ",
9943
- targetName,
9944
- "?"
9839
+ "This stops ",
9840
+ memberName,
9841
+ " spending ",
9842
+ sponsorName,
9843
+ "'s credits immediately."
9945
9844
  ] }),
9946
9845
  /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: MODAL_T.tertiary }, children: [
9947
- targetName,
9948
- " keeps the credits it already received until they expire.",
9949
- 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." : ""
9846
+ "It had ",
9847
+ describeAllowance(member),
9848
+ " and has used ",
9849
+ formatCredits(member.used),
9850
+ ". Credits it already spent are not returned; anything it runs from now uses its own credits."
9950
9851
  ] }),
9951
9852
  /* @__PURE__ */ jsxs23("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9952
9853
  /* @__PURE__ */ jsx26("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Reason" }),
@@ -9954,8 +9855,8 @@ function CancelCreditAllocationModal({
9954
9855
  ScTextArea2,
9955
9856
  {
9956
9857
  value: reason,
9957
- maxLength: CANCEL_REASON_MAX_LENGTH,
9958
- placeholder: "Why is this allocation ending?",
9858
+ maxLength: REMOVE_REASON_MAX_LENGTH,
9859
+ placeholder: "Why is this workspace no longer sharing these credits?",
9959
9860
  disabled: saving,
9960
9861
  onChange: (e) => setReason(e.target.value),
9961
9862
  rows: 3,
@@ -9969,16 +9870,13 @@ function CancelCreditAllocationModal({
9969
9870
  );
9970
9871
  }
9971
9872
 
9972
- // src/credit-allocations-view.tsx
9873
+ // src/shared-credits-view.tsx
9973
9874
  import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
9974
9875
  var T17 = {
9975
9876
  primary: "var(--alias-text---icons-primary)",
9976
9877
  muted: "var(--alias-text---icons-muted)",
9977
9878
  tertiary: "var(--alias-text---icons-tertiary)",
9978
9879
  warning: "var(--alias-text---icons-warning)",
9979
- success: "var(--alias-text---icons-success)",
9980
- warningFill: "var(--alias-fill-warning-soft)",
9981
- warningBorder: "var(--alias-border-warning)",
9982
9880
  fillPlus: "var(--alias-fill-neutral-neutralplus)",
9983
9881
  surfaceBase: "var(--alias-surface-base)",
9984
9882
  borderSubtle: "var(--alias-border-subtle)",
@@ -9992,9 +9890,7 @@ var T17 = {
9992
9890
  r3xl: "var(--radius-3xl)",
9993
9891
  rFull: "var(--radius-full)"
9994
9892
  };
9995
- var GIVING_COLUMNS = "minmax(220px, 1fr) 120px 120px 150px 110px 150px";
9996
- var TRANSFER_COLUMNS = "120px 170px minmax(200px, 1fr) 120px";
9997
- var TRANSFERS_PAGE = 20;
9893
+ var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
9998
9894
  var cellText2 = {
9999
9895
  fontFamily: T17.font,
10000
9896
  fontSize: T17.fsSm,
@@ -10016,138 +9912,67 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ jsx27(
10016
9912
  children: labels.map((label) => /* @__PURE__ */ jsx27("span", { children: label }, label))
10017
9913
  }
10018
9914
  );
10019
- function Badge({ text: text2, tone }) {
10020
- return /* @__PURE__ */ jsx27(
10021
- "span",
10022
- {
10023
- className: "inline-flex items-center shrink-0",
10024
- style: {
10025
- ...cellText2,
10026
- fontSize: 12,
10027
- padding: "0 8px",
10028
- height: 22,
10029
- borderRadius: T17.rFull,
10030
- color: tone === "warning" ? T17.primary : T17.muted,
10031
- background: tone === "warning" ? T17.warningFill : T17.fillPlus,
10032
- border: tone === "warning" ? `1px solid ${T17.warningBorder}` : "1px solid transparent"
10033
- },
10034
- children: text2
10035
- }
10036
- );
10037
- }
10038
- function GivingRow({
10039
- allocation,
10040
- planNotRestored,
9915
+ function MemberRow2({
9916
+ member,
10041
9917
  onEdit,
10042
- onCancel
9918
+ onRemove
10043
9919
  }) {
10044
- const failed = initialTransferWarning(allocation);
10045
- const sent = initialTransferSummary(allocation);
10046
- const active = allocation.status === "active";
9920
+ const spent = member.remaining <= 0;
10047
9921
  return /* @__PURE__ */ jsxs24(
10048
9922
  "div",
10049
9923
  {
10050
9924
  style: {
10051
9925
  borderBottom: `1px solid ${T17.borderSubtle}`,
10052
9926
  display: "grid",
10053
- gridTemplateColumns: GIVING_COLUMNS,
9927
+ gridTemplateColumns: MEMBER_COLUMNS2,
10054
9928
  alignItems: "center",
10055
9929
  padding: `10px ${T17.sp3xl}`,
10056
9930
  gap: 24
10057
9931
  },
10058
9932
  children: [
10059
9933
  /* @__PURE__ */ jsxs24("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
10060
- /* @__PURE__ */ jsx27("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: allocationWorkspaceName(allocation.targetWorkspaceName) }),
10061
- failed ? /* @__PURE__ */ jsxs24("div", { className: "flex flex-col", style: { gap: 2 }, children: [
10062
- /* @__PURE__ */ jsx27("span", { children: /* @__PURE__ */ jsx27(Badge, { text: "First transfer failed", tone: "warning" }) }),
10063
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: failed })
10064
- ] }) : sent ? /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: sent }) : null,
10065
- allocation.note && /* @__PURE__ */ jsx27("span", { className: "truncate", title: allocation.note, style: { ...cellText2, color: T17.tertiary }, children: allocation.note })
10066
- ] }),
10067
- /* @__PURE__ */ jsx27("span", { style: cellText2, children: formatAllocationSplit(allocation) }),
10068
- /* @__PURE__ */ jsx27("span", { style: cellText2, children: formatAllocationRepeat(allocation.recurring) }),
10069
- /* @__PURE__ */ jsxs24("div", { className: "flex flex-wrap items-center", style: { gap: 6 }, children: [
10070
- /* @__PURE__ */ jsx27(Badge, { text: active ? "Active" : "Canceled", tone: "neutral" }),
10071
- planNotRestored && /* @__PURE__ */ jsx27(Badge, { text: "Plan not restored", tone: "warning" })
9934
+ /* @__PURE__ */ jsx27("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: poolWorkspaceName(member.memberWorkspaceName) }),
9935
+ member.note && /* @__PURE__ */ jsx27("span", { className: "truncate", title: member.note, style: { ...cellText2, color: T17.tertiary }, children: member.note })
10072
9936
  ] }),
10073
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(allocation.createdAt) }),
9937
+ /* @__PURE__ */ jsx27("span", { style: cellText2, children: describeAllowance(member) }),
9938
+ /* @__PURE__ */ jsx27("span", { style: cellText2, children: formatCredits(member.used) }),
9939
+ /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: spent ? T17.warning : T17.primary }, children: formatCredits(member.remaining) }),
10074
9940
  /* @__PURE__ */ jsxs24("div", { className: "flex items-center", style: { gap: 8 }, children: [
10075
- active && onEdit && /* @__PURE__ */ jsx27(ScButton12, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
10076
- active && onCancel && /* @__PURE__ */ jsx27(ScButton12, { text: "Cancel", variant: "secondary", size: "sm", onClick: onCancel })
9941
+ onEdit && /* @__PURE__ */ jsx27(ScButton12, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
9942
+ onRemove && /* @__PURE__ */ jsx27(ScButton12, { text: "Remove", variant: "secondary", size: "sm", onClick: onRemove })
10077
9943
  ] })
10078
9944
  ]
10079
9945
  }
10080
9946
  );
10081
9947
  }
10082
- function TransferRow({ transfer }) {
10083
- return /* @__PURE__ */ jsxs24(
10084
- "div",
10085
- {
10086
- style: {
10087
- borderBottom: `1px solid ${T17.borderSubtle}`,
10088
- display: "grid",
10089
- gridTemplateColumns: TRANSFER_COLUMNS,
10090
- alignItems: "center",
10091
- padding: `8px ${T17.sp3xl}`,
10092
- gap: 24
10093
- },
10094
- children: [
10095
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(transfer.createdAt) }),
10096
- /* @__PURE__ */ jsxs24("span", { style: cellText2, children: [
10097
- transfer.direction === "sent" ? "Sent" : "Received",
10098
- " \xB7",
10099
- " ",
10100
- transfer.kind === "initial" ? "Initial" : "Renewal"
10101
- ] }),
10102
- /* @__PURE__ */ jsx27("span", { className: "truncate", style: cellText2, children: transfer.counterpartWorkspaceName ?? (transfer.counterpartWorkspaceId ? "Unknown workspace" : "\u2014") }),
10103
- /* @__PURE__ */ jsxs24("span", { style: { ...cellText2, color: transfer.direction === "received" ? T17.success : T17.primary }, children: [
10104
- transfer.direction === "received" ? "+" : "\u2212",
10105
- formatCredits(transfer.credits)
10106
- ] })
10107
- ]
10108
- }
10109
- );
10110
- }
10111
- function CreditAllocationsView() {
9948
+ function SharedCreditsView() {
10112
9949
  const { api, workspace, toast } = useSettingsAdapters();
10113
9950
  const workspaceId = workspace.current?.id ?? null;
9951
+ const workspaceName = workspace.current?.name?.trim() || "this workspace";
10114
9952
  const [data, setData] = useState19(null);
10115
9953
  const [loading, setLoading] = useState19(true);
10116
9954
  const [failed, setFailed] = useState19(false);
10117
- const [targets, setTargets] = useState19([]);
10118
- const [transfers, setTransfers] = useState19([]);
10119
- const [transfersTotal, setTransfersTotal] = useState19(0);
10120
- const [transfersPage, setTransfersPage] = useState19(0);
10121
- const [transfersFailed, setTransfersFailed] = useState19(false);
10122
- const [loadingMore, setLoadingMore] = useState19(false);
10123
- const [creating, setCreating] = useState19(false);
9955
+ const [candidates, setCandidates] = useState19([]);
9956
+ const [adding, setAdding] = useState19(false);
10124
9957
  const [editing, setEditing] = useState19(null);
10125
- const [canceling, setCanceling] = useState19(null);
10126
- const [planNotRestored, setPlanNotRestored] = useState19(() => /* @__PURE__ */ new Set());
9958
+ const [removing, setRemoving] = useState19(null);
10127
9959
  const latestLoad = useRef8(0);
10128
9960
  const load = useCallback7(
10129
9961
  async ({ refresh = false } = {}) => {
10130
9962
  const loadId = ++latestLoad.current;
10131
- if (!api.fetchCreditAllocations) {
9963
+ if (!api.fetchCreditPool) {
10132
9964
  setLoading(false);
10133
9965
  setFailed(true);
10134
9966
  return;
10135
9967
  }
10136
- const [next, page] = await Promise.all([
10137
- api.fetchCreditAllocations().catch(() => null),
10138
- api.fetchCreditTransfers ? api.fetchCreditTransfers({ page: 1, limit: TRANSFERS_PAGE }).catch(() => null) : Promise.resolve(null)
10139
- ]);
9968
+ const next = await api.fetchCreditPool().catch(() => null);
10140
9969
  if (loadId !== latestLoad.current) return;
10141
9970
  if (refresh && !next) {
10142
- toast.error("Couldn't refresh credit allocations. Reopen this page to see the latest.");
9971
+ toast.error("Couldn't refresh shared credits. Reopen this page to see the latest.");
10143
9972
  return;
10144
9973
  }
10145
9974
  setFailed(!next);
10146
9975
  setData(next);
10147
- setTransfersFailed(Boolean(api.fetchCreditTransfers) && !page);
10148
- setTransfers(page?.transfers ?? []);
10149
- setTransfersTotal(page?.total ?? 0);
10150
- setTransfersPage(page ? 1 : 0);
10151
9976
  setLoading(false);
10152
9977
  },
10153
9978
  [api, toast]
@@ -10156,11 +9981,9 @@ function CreditAllocationsView() {
10156
9981
  setLoading(true);
10157
9982
  setFailed(false);
10158
9983
  setData(null);
10159
- setTransfers([]);
10160
- setCreating(false);
9984
+ setAdding(false);
10161
9985
  setEditing(null);
10162
- setCanceling(null);
10163
- setPlanNotRestored(/* @__PURE__ */ new Set());
9986
+ setRemoving(null);
10164
9987
  void load();
10165
9988
  }, [workspaceId]);
10166
9989
  useEffect16(
@@ -10170,36 +9993,21 @@ function CreditAllocationsView() {
10170
9993
  []
10171
9994
  );
10172
9995
  const canManage = Boolean(data?.canManage);
10173
- const canCreate = canManage && Boolean(api.createCreditAllocation);
10174
- const openCreate = async () => {
10175
- if (!api.fetchAllocationTargets) {
10176
- setTargets([]);
10177
- setCreating(true);
9996
+ const sponsor = data?.sponsor ?? null;
9997
+ const canAdd = canManage && !sponsor && Boolean(api.addCreditPoolMember);
9998
+ const openAdd = async () => {
9999
+ if (!api.fetchCreditPoolCandidates) {
10000
+ setCandidates([]);
10001
+ setAdding(true);
10178
10002
  return;
10179
10003
  }
10180
10004
  try {
10181
- setTargets(await api.fetchAllocationTargets());
10182
- setCreating(true);
10005
+ setCandidates(await api.fetchCreditPoolCandidates());
10006
+ setAdding(true);
10183
10007
  } catch {
10184
10008
  toast.error("Couldn't load this organization's workspaces. Try again.");
10185
10009
  }
10186
10010
  };
10187
- const loadMoreTransfers = async () => {
10188
- if (!api.fetchCreditTransfers || loadingMore) return;
10189
- const loadId = latestLoad.current;
10190
- setLoadingMore(true);
10191
- try {
10192
- const page = await api.fetchCreditTransfers({ page: transfersPage + 1, limit: TRANSFERS_PAGE });
10193
- if (loadId !== latestLoad.current) return;
10194
- setTransfers((prev) => [...prev, ...page.transfers]);
10195
- setTransfersTotal(page.total);
10196
- setTransfersPage(transfersPage + 1);
10197
- } catch {
10198
- toast.error("Couldn't load more transfers. Try again.");
10199
- } finally {
10200
- setLoadingMore(false);
10201
- }
10202
- };
10203
10011
  if (loading) {
10204
10012
  return /* @__PURE__ */ jsx27(
10205
10013
  "div",
@@ -10222,9 +10030,7 @@ function CreditAllocationsView() {
10222
10030
  }
10223
10031
  );
10224
10032
  }
10225
- const giving = data?.giving ?? [];
10226
- const sponsor = data?.receiving.find((a) => a.status === "active") ?? null;
10227
- const balance = typeof workspace.current?.availableCredits === "number" ? workspace.current.availableCredits : null;
10033
+ const members = data?.members ?? [];
10228
10034
  return /* @__PURE__ */ jsxs24("div", { className: "flex flex-col flex-1 min-h-0 w-full overflow-y-auto", style: { gap: 16 }, children: [
10229
10035
  /* @__PURE__ */ jsxs24("div", { className: "flex items-start justify-between shrink-0 w-full", style: { gap: 12 }, children: [
10230
10036
  /* @__PURE__ */ jsxs24("div", { className: "flex flex-col min-w-0", style: { gap: 4 }, children: [
@@ -10232,12 +10038,12 @@ function CreditAllocationsView() {
10232
10038
  "span",
10233
10039
  {
10234
10040
  style: { fontFamily: T17.font, fontSize: T17.fsXl, fontWeight: 500, lineHeight: T17.lhXl, color: T17.primary },
10235
- children: "Credit allocations"
10041
+ children: "Shared credits"
10236
10042
  }
10237
10043
  ),
10238
- /* @__PURE__ */ jsx27("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." })
10044
+ /* @__PURE__ */ jsx27("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." })
10239
10045
  ] }),
10240
- canCreate && !failed && /* @__PURE__ */ jsx27(ScButton12, { text: "New allocation", variant: "primary", size: "md", onClick: () => void openCreate() })
10046
+ canAdd && !failed && /* @__PURE__ */ jsx27(ScButton12, { text: "Share credits", variant: "primary", size: "md", onClick: () => void openAdd() })
10241
10047
  ] }),
10242
10048
  sponsor && /* @__PURE__ */ jsxs24(
10243
10049
  "div",
@@ -10251,103 +10057,74 @@ function CreditAllocationsView() {
10251
10057
  borderRadius: T17.r3xl
10252
10058
  },
10253
10059
  children: [
10254
- /* @__PURE__ */ jsxs24("span", { style: { ...cellText2, fontWeight: 500 }, children: [
10255
- "Funded by ",
10256
- allocationWorkspaceName(sponsor.sourceWorkspaceName)
10257
- ] }),
10060
+ /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: sharedPoolBannerText({
10061
+ sponsorName: poolWorkspaceName(sponsor.sponsorWorkspaceName),
10062
+ allowance: sponsor.allowance,
10063
+ remaining: sponsor.remaining
10064
+ }) }),
10258
10065
  /* @__PURE__ */ jsxs24("span", { style: { ...cellText2, color: T17.muted }, children: [
10259
- "Receives ",
10260
- describeAllocation(sponsor),
10066
+ "This workspace spends ",
10067
+ poolWorkspaceName(sponsor.sponsorWorkspaceName),
10068
+ "'s credits, not its own",
10069
+ sponsor.cycle?.end ? `, until its allowance starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10261
10070
  "."
10262
10071
  ] })
10263
10072
  ]
10264
10073
  }
10265
10074
  ),
10266
- /* @__PURE__ */ jsxs24("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10267
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: "Giving" }),
10075
+ !sponsor && /* @__PURE__ */ jsxs24("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10076
+ /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: "Shared with" }),
10268
10077
  /* @__PURE__ */ jsx27(
10269
10078
  MemberListFrame,
10270
10079
  {
10271
10080
  grow: "cap",
10272
10081
  maxHeight: 420,
10273
- contentMinWidth: 940,
10274
- header: tableHeader(GIVING_COLUMNS, ["Workspace", "Split", "Repeats", "Status", "Created", "Actions"]),
10275
- isEmpty: failed || giving.length === 0,
10276
- emptyText: failed ? "Couldn't load credit allocations. Try again later." : "This workspace doesn't send credits to any other workspace.",
10277
- children: giving.map((allocation) => /* @__PURE__ */ jsx27(
10278
- GivingRow,
10082
+ contentMinWidth: 860,
10083
+ header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Actions"]),
10084
+ isEmpty: failed || members.length === 0,
10085
+ emptyText: failed ? "Couldn't load shared credits. Try again later." : "This workspace doesn't share its credits with any other workspace.",
10086
+ children: members.map((member) => /* @__PURE__ */ jsx27(
10087
+ MemberRow2,
10279
10088
  {
10280
- allocation,
10281
- planNotRestored: planNotRestored.has(allocation._id),
10282
- onEdit: canManage && api.updateCreditAllocation ? () => setEditing(allocation) : void 0,
10283
- onCancel: canManage && api.cancelCreditAllocation ? () => setCanceling(allocation) : void 0
10089
+ member,
10090
+ onEdit: canManage && api.updateCreditPoolMember ? () => setEditing(member) : void 0,
10091
+ onRemove: canManage && api.removeCreditPoolMember ? () => setRemoving(member) : void 0
10284
10092
  },
10285
- allocation._id
10093
+ member._id
10286
10094
  ))
10287
10095
  }
10288
10096
  )
10289
10097
  ] }),
10290
- api.fetchCreditTransfers && !failed && /* @__PURE__ */ jsxs24("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10291
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: "Transfer history" }),
10292
- /* @__PURE__ */ jsx27(
10293
- MemberListFrame,
10294
- {
10295
- grow: "cap",
10296
- maxHeight: 420,
10297
- contentMinWidth: 680,
10298
- header: tableHeader(TRANSFER_COLUMNS, ["Date", "Type", "Workspace", "Credits"]),
10299
- isEmpty: transfersFailed || transfers.length === 0,
10300
- emptyText: transfersFailed ? "Couldn't load transfers. Try again later." : "No transfers yet",
10301
- children: transfers.map((transfer) => /* @__PURE__ */ jsx27(TransferRow, { transfer }, transfer.id))
10302
- }
10303
- ),
10304
- transfers.length < transfersTotal && /* @__PURE__ */ jsx27("div", { children: /* @__PURE__ */ jsx27(
10305
- ScButton12,
10306
- {
10307
- text: loadingMore ? "Loading\u2026" : "Load more",
10308
- variant: "secondary",
10309
- size: "sm",
10310
- state: loadingMore ? "disabled" : "default",
10311
- onClick: loadingMore ? void 0 : () => void loadMoreTransfers()
10312
- }
10313
- ) })
10314
- ] }),
10315
- creating && api.createCreditAllocation && /* @__PURE__ */ jsx27(
10316
- CreditAllocationModal,
10098
+ adding && api.addCreditPoolMember && /* @__PURE__ */ jsx27(
10099
+ CreditPoolMemberModal,
10317
10100
  {
10318
- kind: "create",
10319
- targets,
10320
- balance,
10321
- onSave: (input) => api.createCreditAllocation(input),
10322
- onClose: () => setCreating(false),
10101
+ kind: "add",
10102
+ candidates,
10103
+ onSave: (input) => api.addCreditPoolMember(input),
10104
+ onClose: () => setAdding(false),
10323
10105
  onSaved: () => void load({ refresh: true })
10324
10106
  }
10325
10107
  ),
10326
- editing && api.updateCreditAllocation && /* @__PURE__ */ jsx27(
10327
- CreditAllocationModal,
10108
+ editing && api.updateCreditPoolMember && /* @__PURE__ */ jsx27(
10109
+ CreditPoolMemberModal,
10328
10110
  {
10329
10111
  kind: "edit",
10330
- allocation: editing,
10331
- targetName: allocationWorkspaceName(editing.targetWorkspaceName),
10332
- onSave: (patch) => api.updateCreditAllocation(editing._id, patch),
10112
+ member: editing,
10113
+ memberName: poolWorkspaceName(editing.memberWorkspaceName),
10114
+ onSave: (patch) => api.updateCreditPoolMember(editing._id, patch),
10333
10115
  onClose: () => setEditing(null),
10334
10116
  onSaved: () => void load({ refresh: true })
10335
10117
  }
10336
10118
  ),
10337
- canceling && api.cancelCreditAllocation && /* @__PURE__ */ jsx27(
10338
- CancelCreditAllocationModal,
10119
+ removing && api.removeCreditPoolMember && /* @__PURE__ */ jsx27(
10120
+ RemovePoolMemberModal,
10339
10121
  {
10340
- allocation: canceling,
10341
- targetName: allocationWorkspaceName(canceling.targetWorkspaceName),
10342
- onCancel: (reason) => api.cancelCreditAllocation(canceling._id, reason),
10343
- onClose: () => setCanceling(null),
10344
- onCanceled: ({ planNotRestored: stuck }) => {
10345
- if (stuck) {
10346
- const id = canceling._id;
10347
- setPlanNotRestored((prev) => new Set(prev).add(id));
10348
- }
10349
- void load({ refresh: true });
10350
- }
10122
+ member: removing,
10123
+ memberName: poolWorkspaceName(removing.memberWorkspaceName),
10124
+ sponsorName: workspaceName,
10125
+ onRemove: (reason) => api.removeCreditPoolMember(removing._id, reason),
10126
+ onClose: () => setRemoving(null),
10127
+ onRemoved: () => void load({ refresh: true })
10351
10128
  }
10352
10129
  )
10353
10130
  ] });
@@ -11237,8 +11014,11 @@ function InvoiceHistoryTable({
11237
11014
  function UsageLogsTable({
11238
11015
  logs,
11239
11016
  userMap,
11240
- loading
11017
+ loading,
11018
+ currentWorkspaceId = null,
11019
+ usedByNames = {}
11241
11020
  }) {
11021
+ const showWorkspace = logs.some((log) => usedByWorkspaceKey(log, currentWorkspaceId));
11242
11022
  if (loading) {
11243
11023
  return /* @__PURE__ */ jsx28(
11244
11024
  "div",
@@ -11315,6 +11095,14 @@ function UsageLogsTable({
11315
11095
  style: { flexShrink: 0, width: "7.5rem" }
11316
11096
  }
11317
11097
  ),
11098
+ showWorkspace && /* @__PURE__ */ jsx28(
11099
+ ScHeader,
11100
+ {
11101
+ text: "Workspace",
11102
+ state: "none",
11103
+ style: { flexShrink: 0, width: "9rem" }
11104
+ }
11105
+ ),
11318
11106
  /* @__PURE__ */ jsx28(
11319
11107
  ScHeader,
11320
11108
  {
@@ -11361,6 +11149,8 @@ function UsageLogsTable({
11361
11149
  logs.map((log) => {
11362
11150
  const userName = log.triggeredByUserId ? userMap[log.triggeredByUserId] || "Unknown" : "System";
11363
11151
  const app = log.service?.serviceGroupName || "\u2014";
11152
+ const usedBy = usedByWorkspaceKey(log, currentWorkspaceId);
11153
+ const workspaceLabel2 = usedBy ? `Used by ${usedByNames[usedBy.toLowerCase()] ?? "another workspace"}` : "This workspace";
11364
11154
  const activity = log.type === "return" ? "Returned Credits" : log.meta?.info || "Credit usage";
11365
11155
  const isPositive = log.type === "return" || log.type === "add";
11366
11156
  const signedAmount = `${isPositive ? "+" : "-"}${log.amount}`;
@@ -11390,6 +11180,18 @@ function UsageLogsTable({
11390
11180
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, flex: 1 }, children: app }),
11391
11181
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, flex: 1 }, children: activity }),
11392
11182
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: userName }),
11183
+ showWorkspace && /* @__PURE__ */ jsx28(
11184
+ "div",
11185
+ {
11186
+ style: {
11187
+ ...rowCellRegular,
11188
+ width: "9rem",
11189
+ color: usedBy ? "var(--alias-text---icons-primary, #f5f5f5)" : "var(--alias-text---icons-muted)"
11190
+ },
11191
+ title: workspaceLabel2,
11192
+ children: workspaceLabel2
11193
+ }
11194
+ ),
11393
11195
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: formatDate3(log.createdAt) }),
11394
11196
  /* @__PURE__ */ jsx28(
11395
11197
  "div",
@@ -11458,8 +11260,8 @@ function BillingContent({
11458
11260
  onShowPlansChange,
11459
11261
  showPerUserLimits: showPerUserLimitsProp,
11460
11262
  onShowPerUserLimitsChange,
11461
- showCreditAllocations: showCreditAllocationsProp,
11462
- onShowCreditAllocationsChange
11263
+ showSharedCredits: showSharedCreditsProp,
11264
+ onShowSharedCreditsChange
11463
11265
  } = {}) {
11464
11266
  const { auth, workspace, organization, api, toast, navigation } = useSettingsAdapters();
11465
11267
  const user = auth.user;
@@ -11484,21 +11286,23 @@ function BillingContent({
11484
11286
  if (onShowPerUserLimitsChange) onShowPerUserLimitsChange(next);
11485
11287
  else setShowPerUserLimitsInternal(next);
11486
11288
  };
11487
- const canAllocateCredits = canShowCreditAllocations(
11289
+ const canShareCredits = canShowSharedCredits(
11488
11290
  api,
11489
11291
  workspace.current,
11490
11292
  organization?.current
11491
11293
  );
11492
- const [showCreditAllocationsInternal, setShowCreditAllocationsInternal] = useState20(false);
11493
- const showCreditAllocations = (showCreditAllocationsProp ?? showCreditAllocationsInternal) && canAllocateCredits;
11494
- const setShowCreditAllocations = (next) => {
11495
- if (onShowCreditAllocationsChange) onShowCreditAllocationsChange(next);
11496
- else setShowCreditAllocationsInternal(next);
11294
+ const [showSharedCreditsInternal, setShowSharedCreditsInternal] = useState20(false);
11295
+ const showSharedCredits = (showSharedCreditsProp ?? showSharedCreditsInternal) && canShareCredits;
11296
+ const setShowSharedCredits = (next) => {
11297
+ if (onShowSharedCreditsChange) onShowSharedCreditsChange(next);
11298
+ else setShowSharedCreditsInternal(next);
11497
11299
  };
11498
11300
  const [showExportModal, setShowExportModal] = useState20(false);
11499
11301
  const [showBuyCreditsModal, setShowBuyCreditsModal] = useState20(false);
11500
11302
  const [subscription, setSubscription] = useState20(null);
11501
11303
  const [availableCredits, setAvailableCredits] = useState20(null);
11304
+ const [sharedPool, setSharedPool] = useState20(null);
11305
+ const [usedByNames, setUsedByNames] = useState20({});
11502
11306
  const [products, setProducts] = useState20([]);
11503
11307
  const [invoices, setInvoices] = useState20([]);
11504
11308
  const [totalCredits, setTotalCredits] = useState20(null);
@@ -11549,6 +11353,7 @@ function BillingContent({
11549
11353
  if (subRes?.data) setSubscription(subRes.data);
11550
11354
  if (creditsRes?.availableCredits != null)
11551
11355
  setAvailableCredits(creditsRes.availableCredits);
11356
+ setSharedPool(creditsRes?.sharedPool ?? null);
11552
11357
  if (productsRes?.data) {
11553
11358
  const normalizedWorkspaceCountry = workspace.current?.country?.trim().toUpperCase();
11554
11359
  const targetCurrency = normalizedWorkspaceCountry === "IN" ? "inr" : "usd";
@@ -11597,7 +11402,12 @@ function BillingContent({
11597
11402
  );
11598
11403
  } else if (api.fetchUsageLogs) {
11599
11404
  const res = await api.fetchUsageLogs();
11600
- setUsageLogs(res.data?.length ? res.data : []);
11405
+ const logs = res.data?.length ? res.data : [];
11406
+ setUsageLogs(logs);
11407
+ if (api.fetchCreditPool && logs.some((log) => usedByWorkspaceKey(log, workspaceId))) {
11408
+ const pool = await api.fetchCreditPool().catch(() => null);
11409
+ setUsedByNames(usedByNamesFrom(pool));
11410
+ }
11601
11411
  } else {
11602
11412
  setUsageLogs([]);
11603
11413
  }
@@ -11608,7 +11418,7 @@ function BillingContent({
11608
11418
  setLoadingTable(false);
11609
11419
  }
11610
11420
  },
11611
- [api]
11421
+ [api, workspaceId]
11612
11422
  );
11613
11423
  const handlePlanAction = useCallback8(
11614
11424
  async ({ productId, priceId, planName: planName2 }) => {
@@ -11766,10 +11576,13 @@ function BillingContent({
11766
11576
  );
11767
11577
  const scheduledDowngradeText = subscription?.downgradeScheduled && subscription.plan?.name ? `Downgrades to ${subscription.plan.name}${scheduledDowngradeDateLabel ? ` ${scheduledDowngradeDateLabel}` : ""}` : void 0;
11768
11578
  const isPlanDetailsLoading = !billingOverviewLoaded || loadingBilling || subscription?.plan == null || !planName || planName === "-" || !planAmount || currentPlanCredits == null;
11769
- const usedCredits = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
11770
- const progressPct = usedCredits != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(usedCredits / totalCredits * 100)) : 0;
11771
- const remainingPct = 100 - progressPct;
11772
- const usageText = usedCredits != null ? formatCredits2(usedCredits) : availableCredits != null ? `${formatCredits2(availableCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11579
+ const {
11580
+ used: usedCredits,
11581
+ remaining: remainingCredits,
11582
+ progressPct,
11583
+ remainingPct
11584
+ } = creditsMeter({ availableCredits, totalCredits, sharedPool });
11585
+ const usageText = usedCredits != null ? formatCredits2(usedCredits) : remainingCredits != null ? `${formatCredits2(remainingCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11773
11586
  const handleExport = async (startDate, endDate) => {
11774
11587
  const end = new Date(endDate);
11775
11588
  end.setHours(23, 59, 59, 999);
@@ -11846,7 +11659,7 @@ function BillingContent({
11846
11659
  function fmtFilename(d) {
11847
11660
  return d.toISOString().slice(0, 10);
11848
11661
  }
11849
- async function handleBuyCredits(credits, price) {
11662
+ async function handleBuyCredits(credits2, price) {
11850
11663
  if (!workspaceId) {
11851
11664
  toast.error("Missing workspace details for credit top-up");
11852
11665
  return;
@@ -11858,14 +11671,14 @@ function BillingContent({
11858
11671
  "Buy credits confirmed",
11859
11672
  {
11860
11673
  action: "confirm_buy_credits",
11861
- additionalInfo: `${credits}:${price}`
11674
+ additionalInfo: `${credits2}:${price}`
11862
11675
  }
11863
11676
  );
11864
11677
  try {
11865
11678
  const topupCurrency = workspace.current?.country?.trim().toUpperCase() === "IN" ? "inr" : "usd";
11866
11679
  const res = await api.buyCredits({
11867
11680
  serviceName: DEFAULT_TOPUP_SERVICE,
11868
- credits,
11681
+ credits: credits2,
11869
11682
  unitAmount: price,
11870
11683
  itemCount: 1,
11871
11684
  currency: topupCurrency,
@@ -11883,7 +11696,7 @@ function BillingContent({
11883
11696
  setAvailableCredits(payloadData.availableCredits);
11884
11697
  } else {
11885
11698
  setAvailableCredits(
11886
- (prev) => typeof prev === "number" ? prev + credits : prev
11699
+ (prev) => typeof prev === "number" ? prev + credits2 : prev
11887
11700
  );
11888
11701
  }
11889
11702
  toast.success("Top-up created");
@@ -11953,13 +11766,13 @@ function BillingContent({
11953
11766
  }
11954
11767
  ) });
11955
11768
  }
11956
- if (showCreditAllocations) {
11957
- const allocationsView = /* @__PURE__ */ jsxs25("div", { className: "flex flex-col flex-1 h-full min-h-0 min-w-0 w-full", style: { gap: 16 }, children: [
11958
- !onShowCreditAllocationsChange && /* @__PURE__ */ jsx28(BackToBilling, { onClick: () => setShowCreditAllocations(false) }),
11959
- /* @__PURE__ */ jsx28(CreditAllocationsView, {})
11769
+ if (showSharedCredits) {
11770
+ const sharedCreditsView = /* @__PURE__ */ jsxs25("div", { className: "flex flex-col flex-1 h-full min-h-0 min-w-0 w-full", style: { gap: 16 }, children: [
11771
+ !onShowSharedCreditsChange && /* @__PURE__ */ jsx28(BackToBilling, { onClick: () => setShowSharedCredits(false) }),
11772
+ /* @__PURE__ */ jsx28(SharedCreditsView, {})
11960
11773
  ] });
11961
- if (embedded) return allocationsView;
11962
- return /* @__PURE__ */ jsx28("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: allocationsView });
11774
+ if (embedded) return sharedCreditsView;
11775
+ return /* @__PURE__ */ jsx28("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: sharedCreditsView });
11963
11776
  }
11964
11777
  if (showPerUserLimits) {
11965
11778
  const limitsView = /* @__PURE__ */ jsxs25("div", { className: "flex flex-col flex-1 h-full min-h-0 min-w-0 w-full", style: { gap: 16 }, children: [
@@ -12026,7 +11839,7 @@ function BillingContent({
12026
11839
  {
12027
11840
  usageLabel: "Credits used",
12028
11841
  usageText,
12029
- remainingText: availableCredits != null ? `${formatCredits2(availableCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
11842
+ remainingText: remainingCredits != null ? `${formatCredits2(remainingCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
12030
11843
  buyButtonText: "Buy credits",
12031
11844
  buyButtonState: isFreePlan ? "disabled" : "default",
12032
11845
  buyButtonHoverText: isFreePlan ? "Pro plan required" : void 0,
@@ -12046,6 +11859,50 @@ function BillingContent({
12046
11859
  ]
12047
11860
  }
12048
11861
  ),
11862
+ sharedPool ? /* @__PURE__ */ jsxs25(
11863
+ "div",
11864
+ {
11865
+ role: "status",
11866
+ className: "flex flex-col shrink-0 w-full",
11867
+ style: {
11868
+ gap: 4,
11869
+ padding: 16,
11870
+ backgroundColor: "var(--alias-surface-base)",
11871
+ border: "1px solid var(--alias-border-subtle)",
11872
+ borderRadius: "var(--radius-3xl)"
11873
+ },
11874
+ children: [
11875
+ /* @__PURE__ */ jsx28(
11876
+ "span",
11877
+ {
11878
+ style: {
11879
+ fontFamily: "var(--font-family-font-family-body)",
11880
+ fontSize: "var(--font-size-font-size-sm)",
11881
+ fontWeight: 500,
11882
+ lineHeight: "var(--line-height-line-height-sm)",
11883
+ color: "var(--alias-text---icons-primary)"
11884
+ },
11885
+ children: sharedPoolBannerText(sharedPool)
11886
+ }
11887
+ ),
11888
+ /* @__PURE__ */ jsxs25(
11889
+ "span",
11890
+ {
11891
+ style: {
11892
+ fontFamily: "var(--font-family-font-family-body)",
11893
+ fontSize: "var(--font-size-font-size-sm)",
11894
+ lineHeight: "var(--line-height-line-height-sm)",
11895
+ color: "var(--alias-text---icons-muted)"
11896
+ },
11897
+ children: [
11898
+ pausedOwnCreditsText(sharedPool),
11899
+ sharedPool.cycleEnd ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : ""
11900
+ ]
11901
+ }
11902
+ )
11903
+ ]
11904
+ }
11905
+ ) : null,
12049
11906
  usage ? /* @__PURE__ */ jsxs25(
12050
11907
  "div",
12051
11908
  {
@@ -12119,7 +11976,7 @@ function BillingContent({
12119
11976
  ]
12120
11977
  }
12121
11978
  ) : null,
12122
- canAllocateCredits ? /* @__PURE__ */ jsxs25(
11979
+ canShareCredits ? /* @__PURE__ */ jsxs25(
12123
11980
  "div",
12124
11981
  {
12125
11982
  className: "flex items-center justify-between shrink-0 w-full",
@@ -12142,7 +11999,7 @@ function BillingContent({
12142
11999
  lineHeight: "var(--line-height-line-height-sm)",
12143
12000
  color: "var(--alias-text---icons-primary)"
12144
12001
  },
12145
- children: "Credit allocations"
12002
+ children: "Shared credits"
12146
12003
  }
12147
12004
  ),
12148
12005
  /* @__PURE__ */ jsx28(
@@ -12154,7 +12011,7 @@ function BillingContent({
12154
12011
  lineHeight: "var(--line-height-line-height-sm)",
12155
12012
  color: "var(--alias-text---icons-muted)"
12156
12013
  },
12157
- children: "Share this workspace's credits with other workspaces in your organization"
12014
+ children: "Let other workspaces in your organization spend this workspace's credits"
12158
12015
  }
12159
12016
  )
12160
12017
  ] }),
@@ -12164,7 +12021,7 @@ function BillingContent({
12164
12021
  text: "Manage",
12165
12022
  variant: "secondary",
12166
12023
  size: "md",
12167
- onClick: () => setShowCreditAllocations(true)
12024
+ onClick: () => setShowSharedCredits(true)
12168
12025
  }
12169
12026
  )
12170
12027
  ]
@@ -12214,7 +12071,9 @@ function BillingContent({
12214
12071
  {
12215
12072
  logs: usageLogs,
12216
12073
  userMap,
12217
- loading: loadingTable
12074
+ loading: loadingTable,
12075
+ currentWorkspaceId: workspaceId,
12076
+ usedByNames
12218
12077
  }
12219
12078
  ),
12220
12079
  showExportModal && /* @__PURE__ */ jsx28(
@@ -12233,8 +12092,8 @@ function BillingContent({
12233
12092
  workspace.current?.country
12234
12093
  ),
12235
12094
  onClose: () => setShowBuyCreditsModal(false),
12236
- onBuy: (credits, price) => {
12237
- handleBuyCredits(credits, price);
12095
+ onBuy: (credits2, price) => {
12096
+ handleBuyCredits(credits2, price);
12238
12097
  }
12239
12098
  }
12240
12099
  )
@@ -13259,7 +13118,7 @@ function ConnectedAppsContent() {
13259
13118
 
13260
13119
  // src/organization-integrations-content.tsx
13261
13120
  import { useState as useState23 } from "react";
13262
- import { ScButton as ScButton16, ScToggleSwitch as ScToggleSwitch2 } from "@streamoid/ui";
13121
+ import { ScButton as ScButton16, ScToggleSwitch } from "@streamoid/ui";
13263
13122
  import { Fragment as Fragment14, jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
13264
13123
  var COLUMN = { app: "6.5rem", workspace: "9rem", date: "6.5rem", action: "4.75rem" };
13265
13124
  function HeaderRow() {
@@ -13388,7 +13247,7 @@ function AccessCard({
13388
13247
  /* @__PURE__ */ jsxs28("div", { className: "flex items-center shrink-0", style: { alignSelf: "center", gap: T15.spMd, minHeight: 26 }, children: [
13389
13248
  /* @__PURE__ */ jsx31("span", { style: { fontFamily: T15.font, fontSize: T15.fsSm, lineHeight: T15.lhSm, color: T15.muted }, children: enabled === null ? "" : enabled ? "On" : "Off" }),
13390
13249
  /* @__PURE__ */ jsx31("span", { style: { display: "inline-flex", width: 41, height: 25, alignItems: "center" }, children: /* @__PURE__ */ jsx31(
13391
- ScToggleSwitch2,
13250
+ ScToggleSwitch,
13392
13251
  {
13393
13252
  checked: !!enabled,
13394
13253
  disabled: enabled === null || loading,
@@ -14888,20 +14747,20 @@ function SettingsContentInner({
14888
14747
  const isBillingPlans = activeTab === "billing" && isWorkspaceAdmin && billingShowPlans;
14889
14748
  const [billingShowPerUserLimits, setBillingShowPerUserLimits] = useState24(false);
14890
14749
  const isBillingPerUserLimits = activeTab === "billing" && isWorkspaceAdmin && billingShowPerUserLimits && !billingShowPlans;
14891
- const [billingShowCreditAllocations, setBillingShowCreditAllocations] = useState24(false);
14892
- const isBillingCreditAllocations = activeTab === "billing" && isWorkspaceAdmin && billingShowCreditAllocations && !billingShowPlans && !billingShowPerUserLimits;
14893
- const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingCreditAllocations;
14750
+ const [billingShowSharedCredits, setBillingShowSharedCredits] = useState24(false);
14751
+ const isBillingSharedCredits = activeTab === "billing" && isWorkspaceAdmin && billingShowSharedCredits && !billingShowPlans && !billingShowPerUserLimits;
14752
+ const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingSharedCredits;
14894
14753
  useEffect19(() => {
14895
14754
  if (activeTab !== "billing" || !isWorkspaceAdmin) {
14896
14755
  setBillingShowPlans(false);
14897
14756
  setBillingShowPerUserLimits(false);
14898
- setBillingShowCreditAllocations(false);
14757
+ setBillingShowSharedCredits(false);
14899
14758
  }
14900
14759
  }, [activeTab, isWorkspaceAdmin]);
14901
14760
  useEffect19(() => {
14902
14761
  setBillingShowPlans(false);
14903
14762
  setBillingShowPerUserLimits(false);
14904
- setBillingShowCreditAllocations(false);
14763
+ setBillingShowSharedCredits(false);
14905
14764
  }, [workspaceId]);
14906
14765
  useEffect19(() => {
14907
14766
  if (!onClose) return;
@@ -14916,15 +14775,15 @@ function SettingsContentInner({
14916
14775
  setBillingShowPerUserLimits(false);
14917
14776
  return;
14918
14777
  }
14919
- if (billingShowCreditAllocations) {
14920
- setBillingShowCreditAllocations(false);
14778
+ if (billingShowSharedCredits) {
14779
+ setBillingShowSharedCredits(false);
14921
14780
  return;
14922
14781
  }
14923
14782
  onClose();
14924
14783
  };
14925
14784
  document.addEventListener("keydown", handleKeyDown);
14926
14785
  return () => document.removeEventListener("keydown", handleKeyDown);
14927
- }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowCreditAllocations]);
14786
+ }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowSharedCredits]);
14928
14787
  const [localWorkspaces, setLocalWorkspaces] = useState24(null);
14929
14788
  const [wsModalOpen, setWsModalOpen] = useState24(false);
14930
14789
  const [wsModalMode, setWsModalMode] = useState24("create");
@@ -15245,7 +15104,7 @@ function SettingsContentInner({
15245
15104
  if (isBillingSubView) {
15246
15105
  setBillingShowPlans(false);
15247
15106
  setBillingShowPerUserLimits(false);
15248
- setBillingShowCreditAllocations(false);
15107
+ setBillingShowSharedCredits(false);
15249
15108
  return;
15250
15109
  }
15251
15110
  if (onBack) onBack();
@@ -15348,7 +15207,7 @@ function SettingsContentInner({
15348
15207
  }
15349
15208
  )
15350
15209
  ] }),
15351
- isBillingCreditAllocations && /* @__PURE__ */ jsxs29(Fragment16, { children: [
15210
+ isBillingSharedCredits && /* @__PURE__ */ jsxs29(Fragment16, { children: [
15352
15211
  /* @__PURE__ */ jsx32(SiconRight3, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15353
15212
  /* @__PURE__ */ jsx32(
15354
15213
  "span",
@@ -15360,7 +15219,7 @@ function SettingsContentInner({
15360
15219
  lineHeight: "var(--line-height-line-height-xl)",
15361
15220
  color: "var(--alias-text---icons-primary)"
15362
15221
  },
15363
- children: "Credit allocations"
15222
+ children: "Shared credits"
15364
15223
  }
15365
15224
  )
15366
15225
  ] })
@@ -15573,7 +15432,7 @@ function SettingsContentInner({
15573
15432
  setBillingShowPlans(open);
15574
15433
  if (open) {
15575
15434
  setBillingShowPerUserLimits(false);
15576
- setBillingShowCreditAllocations(false);
15435
+ setBillingShowSharedCredits(false);
15577
15436
  }
15578
15437
  },
15579
15438
  showPerUserLimits: billingShowPerUserLimits,
@@ -15581,12 +15440,12 @@ function SettingsContentInner({
15581
15440
  setBillingShowPerUserLimits(open);
15582
15441
  if (open) {
15583
15442
  setBillingShowPlans(false);
15584
- setBillingShowCreditAllocations(false);
15443
+ setBillingShowSharedCredits(false);
15585
15444
  }
15586
15445
  },
15587
- showCreditAllocations: billingShowCreditAllocations,
15588
- onShowCreditAllocationsChange: (open) => {
15589
- setBillingShowCreditAllocations(open);
15446
+ showSharedCredits: billingShowSharedCredits,
15447
+ onShowSharedCreditsChange: (open) => {
15448
+ setBillingShowSharedCredits(open);
15590
15449
  if (open) {
15591
15450
  setBillingShowPlans(false);
15592
15451
  setBillingShowPerUserLimits(false);
@@ -16342,18 +16201,13 @@ function OrganizationPicker({
16342
16201
  );
16343
16202
  }
16344
16203
  export {
16345
- ALLOCATION_ERROR_CODES,
16346
- ALLOCATION_NOTE_MAX_LENGTH,
16347
16204
  APP_KEYS,
16348
16205
  BillingContent,
16349
16206
  BuyCreditsModal,
16350
- CANCEL_REASON_MAX_LENGTH,
16351
- CancelCreditAllocationModal,
16352
16207
  ConfirmDeleteModal,
16353
16208
  ConfirmLeaveModal,
16354
16209
  ConnectedAppsContent,
16355
- CreditAllocationModal,
16356
- CreditAllocationsView,
16210
+ CreditPoolMemberModal,
16357
16211
  DEFAULT_PERMISSIONS,
16358
16212
  DEFAULT_USER_LANGUAGE,
16359
16213
  ExportDateModal,
@@ -16366,15 +16220,20 @@ export {
16366
16220
  OrganizationMemberModal,
16367
16221
  OrganizationModal,
16368
16222
  OrganizationPicker,
16223
+ POOL_ERROR_CODES,
16224
+ POOL_NOTE_MAX_LENGTH,
16369
16225
  PerUserLimitsView,
16370
16226
  ProfileAvatar,
16371
16227
  ProfileTab,
16228
+ REMOVE_REASON_MAX_LENGTH,
16372
16229
  ReferralTab,
16230
+ RemovePoolMemberModal,
16373
16231
  SCOPE_LABELS,
16374
16232
  ScopeHeader,
16375
16233
  SettingsAdaptersProvider,
16376
16234
  SettingsContent,
16377
16235
  SettingsTooltip,
16236
+ SharedCreditsView,
16378
16237
  SiconKey_default as SiconKey,
16379
16238
  TAB_DESCRIPTIONS,
16380
16239
  TAB_LABELS,
@@ -16390,28 +16249,22 @@ export {
16390
16249
  WorkspaceModal,
16391
16250
  WorkspacePicker,
16392
16251
  WorkspaceTab,
16393
- allocationErrorMessage,
16394
- allocationWorkspaceName,
16395
16252
  belowUsageReason,
16396
- canShowCreditAllocations,
16397
- cancelOutcomeSummary,
16398
- cancelOutcomeWarning,
16399
- cleanAllocationValueInput,
16253
+ canShowSharedCredits,
16400
16254
  cleanCreditLimitInput,
16255
+ cleanPoolValueInput,
16401
16256
  countLabel,
16402
16257
  creditCycleUnit,
16403
- describeAllocation,
16258
+ creditsMeter,
16259
+ describeAllowance,
16404
16260
  describeDefaultLimit,
16405
16261
  describeMemberLimit,
16406
- findAllocationError,
16407
16262
  findBillingError,
16408
- formatAllocationRepeat,
16409
- formatAllocationSplit,
16263
+ findPoolError,
16410
16264
  formatCreditLimit,
16411
16265
  formatCredits,
16266
+ formatPoolSplit,
16412
16267
  groupWorkspaceList,
16413
- initialTransferSummary,
16414
- initialTransferWarning,
16415
16268
  isLimitBelowUsage,
16416
16269
  joinMembersWithLimits,
16417
16270
  lastUsedLabel,
@@ -16420,13 +16273,19 @@ export {
16420
16273
  loadWorkspaceOrganization,
16421
16274
  memberRoleUpdate,
16422
16275
  minimumCreditLimit,
16276
+ pausedOwnCreditsText,
16277
+ poolErrorMessage,
16278
+ poolWorkspaceName,
16423
16279
  resolveSettingsTabFromPath,
16424
16280
  roundCredits,
16281
+ sharedPoolBannerText,
16425
16282
  useSettingsAdapters,
16426
16283
  useWorkspaceOrganization,
16284
+ usedByNamesFrom,
16285
+ usedByWorkspaceKey,
16427
16286
  userLanguageLabel,
16428
- validateAllocationText,
16429
- validateAllocationValue,
16430
16287
  validateCreditLimitInput,
16431
- validateDefaultCreditLimitInput
16288
+ validateDefaultCreditLimitInput,
16289
+ validatePoolText,
16290
+ validatePoolValue
16432
16291
  };