@streamoid/settings 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7148,6 +7148,8 @@ function OrganizationListContent() {
7148
7148
  const [createOpen, setCreateOpen] = useState11(false);
7149
7149
  const [managing, setManaging] = useState11(null);
7150
7150
  const [openingId, setOpeningId] = useState11(null);
7151
+ const [joinable, setJoinable] = useState11([]);
7152
+ const [joiningId, setJoiningId] = useState11(null);
7151
7153
  const organizationRef = useRef6(organization);
7152
7154
  organizationRef.current = organization;
7153
7155
  const load = useCallback4(async () => {
@@ -7160,6 +7162,32 @@ function OrganizationListContent() {
7160
7162
  setLoading(false);
7161
7163
  }
7162
7164
  }, []);
7165
+ const loadJoinable = useCallback4(async () => {
7166
+ const list = organizationRef.current.listJoinable;
7167
+ if (!list || !organizationRef.current.join) return;
7168
+ try {
7169
+ setJoinable(await list());
7170
+ } catch {
7171
+ setJoinable([]);
7172
+ }
7173
+ }, []);
7174
+ useEffect11(() => {
7175
+ void loadJoinable();
7176
+ }, [loadJoinable]);
7177
+ const joinOrganization = async (org) => {
7178
+ const join = organizationRef.current.join;
7179
+ if (!join) return;
7180
+ setJoiningId(org._id);
7181
+ try {
7182
+ await join(org._id);
7183
+ toast.success(`Joined "${org.name}". Open it to pick a workspace.`);
7184
+ await Promise.all([load(), loadJoinable()]);
7185
+ } catch (err) {
7186
+ toast.error(errorMessage(err, { method: "POST" }));
7187
+ } finally {
7188
+ setJoiningId(null);
7189
+ }
7190
+ };
7163
7191
  useEffect11(() => {
7164
7192
  void load();
7165
7193
  }, [organization.current?.id]);
@@ -7291,6 +7319,52 @@ function OrganizationListContent() {
7291
7319
  ))
7292
7320
  }
7293
7321
  ),
7322
+ joinable.length > 0 && /* @__PURE__ */ jsxs16("div", { className: "flex flex-col w-full", style: { gap: T12.spXl }, children: [
7323
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-col min-w-0", style: { gap: T12.spXs }, children: [
7324
+ /* @__PURE__ */ jsx19("p", { style: { margin: 0, color: T12.primary, fontSize: T12.fsLg, fontWeight: 600, lineHeight: T12.lhLg }, children: "Other organizations" }),
7325
+ /* @__PURE__ */ jsx19("p", { style: { margin: 0, color: T12.muted, fontSize: T12.fsSm, lineHeight: T12.lhSm }, children: "Shared organizations at your company that you can join as a member." })
7326
+ ] }),
7327
+ /* @__PURE__ */ jsx19(
7328
+ "div",
7329
+ {
7330
+ className: "flex flex-col w-full",
7331
+ style: {
7332
+ background: T12.surface,
7333
+ border: `1px solid ${T12.borderSubtle}`,
7334
+ borderRadius: T12.r3xl
7335
+ },
7336
+ children: joinable.map((org, index) => /* @__PURE__ */ jsxs16(
7337
+ "div",
7338
+ {
7339
+ className: "flex items-center w-full",
7340
+ style: {
7341
+ gap: T12.spXl,
7342
+ padding: `${T12.spXl} ${T12.sp3xl}`,
7343
+ borderTop: index ? `1px solid ${T12.borderSubtle}` : "none"
7344
+ },
7345
+ children: [
7346
+ /* @__PURE__ */ jsx19(ProfileAvatar, { name: org.name, profileImage: org.imageUrl, size: 36, borderRadius: T12.rLg }),
7347
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-col flex-1 min-w-0", children: [
7348
+ /* @__PURE__ */ jsx19("span", { className: "truncate", style: { color: T12.primary, fontSize: T12.fsSm, fontWeight: 500, lineHeight: T12.lhSm }, children: org.name }),
7349
+ /* @__PURE__ */ jsx19("span", { className: "truncate", style: { color: T12.muted, fontSize: T12.fsXs, lineHeight: T12.lhXs }, children: ["Shared", org.domain].filter(Boolean).join(" \xB7 ") })
7350
+ ] }),
7351
+ /* @__PURE__ */ jsx19(
7352
+ ScButton6,
7353
+ {
7354
+ text: joiningId === org._id ? "Joining\u2026" : "Join",
7355
+ variant: "secondary",
7356
+ size: "sm",
7357
+ state: joiningId ? "disabled" : "default",
7358
+ onClick: joiningId ? void 0 : () => void joinOrganization(org)
7359
+ }
7360
+ )
7361
+ ]
7362
+ },
7363
+ org._id
7364
+ ))
7365
+ }
7366
+ )
7367
+ ] }),
7294
7368
  /* @__PURE__ */ jsx19(
7295
7369
  OrganizationModal,
7296
7370
  {
@@ -7913,14 +7987,14 @@ function BuyCreditsModal({
7913
7987
  const maxCredits = Math.round(
7914
7988
  topupConfig.maxAmount / topupConfig.pricePerCredit
7915
7989
  );
7916
- const [credits, setCredits] = useState13(minCredits);
7990
+ const [credits2, setCredits] = useState13(minCredits);
7917
7991
  const [creditsInput, setCreditsInput] = useState13(String(minCredits));
7918
7992
  const [priceInput, setPriceInput] = useState13(
7919
7993
  String(minCredits * topupConfig.pricePerCredit)
7920
7994
  );
7921
7995
  const [activeField, setActiveField] = useState13("credits");
7922
- const parsedCredits = creditsInput.length ? Number(creditsInput) : credits;
7923
- const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits;
7996
+ const parsedCredits = creditsInput.length ? Number(creditsInput) : credits2;
7997
+ const previewCredits = Number.isFinite(parsedCredits) ? parsedCredits : credits2;
7924
7998
  const pricedCredits = previewCredits > 0 ? previewCredits : 0;
7925
7999
  const price = Number(
7926
8000
  (pricedCredits * topupConfig.pricePerCredit).toFixed(2)
@@ -7942,8 +8016,8 @@ function BuyCreditsModal({
7942
8016
  setCreditsInput(String(derived));
7943
8017
  setPriceInput(String(amount));
7944
8018
  };
7945
- const handleDecrement = () => syncFromCredits(Math.max(minCredits, credits - STEP));
7946
- 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));
7947
8021
  const handleCreditsChange = (value) => {
7948
8022
  const raw = value.replace(/[^\d]/g, "");
7949
8023
  setCreditsInput(raw);
@@ -8077,7 +8151,7 @@ function BuyCreditsModal({
8077
8151
  style: {
8078
8152
  width: 24,
8079
8153
  height: 24,
8080
- opacity: credits <= minCredits ? 0.3 : 1,
8154
+ opacity: credits2 <= minCredits ? 0.3 : 1,
8081
8155
  transition: "opacity 0.15s ease"
8082
8156
  },
8083
8157
  onClick: handleDecrement,
@@ -8135,7 +8209,7 @@ function BuyCreditsModal({
8135
8209
  style: {
8136
8210
  width: 24,
8137
8211
  height: 24,
8138
- opacity: credits >= maxCredits ? 0.3 : 1,
8212
+ opacity: credits2 >= maxCredits ? 0.3 : 1,
8139
8213
  transition: "opacity 0.15s ease"
8140
8214
  },
8141
8215
  onClick: handleIncrement,
@@ -8267,7 +8341,7 @@ function BuyCreditsModal({
8267
8341
  pointerEvents: isInvalid ? "none" : "auto",
8268
8342
  transition: "opacity 0.2s ease"
8269
8343
  },
8270
- onClick: () => onBuy(credits, price),
8344
+ onClick: () => onBuy(credits2, price),
8271
8345
  children: /* @__PURE__ */ jsx21(
8272
8346
  "span",
8273
8347
  {
@@ -9007,7 +9081,7 @@ function PerUserLimitsView() {
9007
9081
  ] });
9008
9082
  }
9009
9083
 
9010
- // src/credit-allocations-view.tsx
9084
+ // src/shared-credits-view.tsx
9011
9085
  import { useCallback as useCallback7, useEffect as useEffect16, useRef as useRef8, useState as useState19 } from "react";
9012
9086
  import { ScButton as ScButton12 } from "@streamoid/ui";
9013
9087
 
@@ -9256,23 +9330,23 @@ function ConfirmActionModal({
9256
9330
  );
9257
9331
  }
9258
9332
 
9259
- // src/credit-allocation-modal.tsx
9333
+ // src/credit-pool-modal.tsx
9260
9334
  import { useEffect as useEffect15, useState as useState17 } from "react";
9261
- 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";
9262
9336
  import { SiconClose as SiconClose8 } from "@streamoid/icons";
9263
9337
 
9264
- // src/credit-allocations.ts
9265
- var ALLOCATION_NOTE_MAX_LENGTH = 500;
9266
- var CANCEL_REASON_MAX_LENGTH = 500;
9267
- function canShowCreditAllocations(api, workspace, organization) {
9268
- 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);
9269
9343
  }
9270
- function cleanAllocationValueInput(raw) {
9344
+ function cleanPoolValueInput(raw) {
9271
9345
  const kept = raw.replace(/[^\d.]/g, "");
9272
9346
  const dot = kept.indexOf(".");
9273
9347
  return dot === -1 ? kept : kept.slice(0, dot + 1) + kept.slice(dot + 1).replace(/\./g, "");
9274
9348
  }
9275
- function validateAllocationValue(mode, raw, balance) {
9349
+ function validatePoolValue(mode, raw) {
9276
9350
  const trimmed = raw.trim();
9277
9351
  if (!trimmed) return { ok: false, reason: mode === "percent" ? "Enter a percentage" : "Enter a number of credits" };
9278
9352
  if (!/^\d+(\.\d{1,2})?$/.test(trimmed)) {
@@ -9281,64 +9355,61 @@ function validateAllocationValue(mode, raw, balance) {
9281
9355
  const value = Number(trimmed);
9282
9356
  if (!(value > 0)) return { ok: false, reason: "Enter a number greater than 0" };
9283
9357
  if (mode === "percent" && value > 100) return { ok: false, reason: "A percentage can be at most 100" };
9284
- if (mode === "fixed" && typeof balance === "number" && Number.isFinite(balance) && value > balance) {
9285
- return {
9286
- ok: false,
9287
- reason: `This workspace has ${formatCredits(balance)} credits available`
9288
- };
9289
- }
9290
9358
  return { ok: true, value };
9291
9359
  }
9292
- function validateAllocationText(raw, max) {
9360
+ function validatePoolText(raw, max) {
9293
9361
  const value = raw.trim();
9294
9362
  if (!value) return { ok: false, reason: "Required" };
9295
9363
  if (value.length > max) return { ok: false, reason: `At most ${max} characters` };
9296
9364
  return { ok: true, value };
9297
9365
  }
9298
- function formatAllocationSplit(split) {
9366
+ var credits = (value) => `${formatCredits(value)} ${value === 1 ? "credit" : "credits"}`;
9367
+ function formatPoolSplit(split) {
9299
9368
  if (split.mode === "percent") return `${formatCredits(split.value)}%`;
9300
- return `${formatCredits(split.value)} ${split.value === 1 ? "credit" : "credits"}`;
9301
- }
9302
- function formatAllocationRepeat(recurring) {
9303
- return recurring ? "Every renewal" : "Once";
9369
+ return credits(split.value);
9304
9370
  }
9305
- function describeAllocation(allocation) {
9306
- const split = formatAllocationSplit(allocation);
9307
- if (!allocation.recurring) return `${split} once`;
9308
- return allocation.mode === "percent" ? `${split} of each renewal` : `${split} every renewal`;
9371
+ function describeAllowance(member) {
9372
+ const allowance = `${credits(member.allowance)} this cycle`;
9373
+ return member.mode === "percent" ? `${formatPoolSplit(member)} \xB7 ${allowance}` : allowance;
9309
9374
  }
9310
- function allocationWorkspaceName(name) {
9375
+ function poolWorkspaceName(name) {
9311
9376
  return name?.trim() || "Unknown workspace";
9312
9377
  }
9313
- function initialTransferWarning(allocation) {
9314
- const transfer = allocation.initialTransfer;
9315
- if (transfer?.status !== "failed") return null;
9316
- const why = transfer.error?.trim();
9317
- return `The first transfer failed${why ? `: ${why}` : ""}. The allocation is active and renewals will still split, but these credits did not move.`;
9318
- }
9319
- function initialTransferSummary(allocation) {
9320
- const transfer = allocation.initialTransfer;
9321
- if (transfer?.status !== "done") return null;
9322
- return `Sent ${formatCredits(transfer.credits)} ${transfer.credits === 1 ? "credit" : "credits"} at setup`;
9323
- }
9324
- function cancelOutcomeWarning(result, targetName) {
9325
- const stuck = !result.targetMovedToFree && !result.reason?.trim() && result.allocation?.targetPlanChanged === true;
9326
- if (!result.error && !stuck) return null;
9327
- 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.`;
9328
- }
9329
- function cancelOutcomeSummary(result, targetName) {
9330
- const why = result.reason?.trim();
9331
- if (result.targetMovedToFree) return `Allocation canceled. ${targetName} is back on the free plan.`;
9332
- return why ? `Allocation canceled. ${why.replace(/\.?$/, ".")}` : "Allocation canceled";
9333
- }
9334
- var ALLOCATION_ERROR_CODES = [
9335
- "INSUFFICIENT_CREDITS",
9336
- "SOURCE_ON_FREE_PLAN",
9337
- "ALLOCATION_NOT_ALLOWED",
9338
- "TARGET_PLAN_CHANGE_FAILED"
9339
- ];
9340
- function findAllocationError(failure) {
9341
- const error = findBillingError(failure, ALLOCATION_ERROR_CODES);
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`;
9381
+ }
9382
+ function creditsMeter({
9383
+ availableCredits,
9384
+ totalCredits,
9385
+ sharedPool
9386
+ }) {
9387
+ if (sharedPool) {
9388
+ const used2 = Math.max(0, sharedPool.used);
9389
+ const remaining = Math.max(0, sharedPool.remaining);
9390
+ const progressPct2 = sharedPool.allowance > 0 ? Math.min(100, Math.round(used2 / sharedPool.allowance * 100)) : 100;
9391
+ return { used: used2, remaining, progressPct: progressPct2, remainingPct: 100 - progressPct2 };
9392
+ }
9393
+ const used = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
9394
+ const progressPct = used != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(used / totalCredits * 100)) : 0;
9395
+ return { used, remaining: availableCredits, progressPct, remainingPct: 100 - progressPct };
9396
+ }
9397
+ function usedByWorkspaceKey(log, currentWorkspaceId) {
9398
+ const usedBy = log.meta?.usedBy;
9399
+ if (typeof usedBy !== "string" || !usedBy) return null;
9400
+ return usedBy.toLowerCase() === String(currentWorkspaceId ?? "").toLowerCase() ? null : usedBy;
9401
+ }
9402
+ function usedByNamesFrom(pool) {
9403
+ const names = {};
9404
+ for (const member of [...pool?.formerMembers ?? [], ...pool?.members ?? []]) {
9405
+ const name = member.memberWorkspaceName?.trim();
9406
+ if (name && member.memberWorkspaceKey) names[member.memberWorkspaceKey.toLowerCase()] = name;
9407
+ }
9408
+ return names;
9409
+ }
9410
+ var POOL_ERROR_CODES = ["SPONSOR_ON_FREE_PLAN", "POOL_NOT_ALLOWED"];
9411
+ function findPoolError(failure) {
9412
+ const error = findBillingError(failure, POOL_ERROR_CODES);
9342
9413
  if (!error) return null;
9343
9414
  const status = error.status;
9344
9415
  const message = [status?.message, error.error, error.message].find(
@@ -9347,29 +9418,18 @@ function findAllocationError(failure) {
9347
9418
  const details = error.details && typeof error.details === "object" ? error.details : {};
9348
9419
  return { code: error.code, details, message: message ?? null };
9349
9420
  }
9350
- var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
9351
- function allocationErrorMessage(failure) {
9352
- const found = findAllocationError(failure);
9421
+ function poolErrorMessage(failure) {
9422
+ const found = findPoolError(failure);
9353
9423
  if (!found) return null;
9354
9424
  switch (found.code) {
9355
- case "INSUFFICIENT_CREDITS": {
9356
- const balance = num(found.details.currentBalance);
9357
- const required = num(found.details.requiredCredits);
9358
- if (balance !== null && required !== null) {
9359
- return `Not enough credits: this would send ${formatCredits(required)} now, but the workspace has ${formatCredits(balance)}.`;
9360
- }
9361
- return "This workspace doesn't have enough credits to send that now.";
9362
- }
9363
- case "SOURCE_ON_FREE_PLAN":
9425
+ case "SPONSOR_ON_FREE_PLAN":
9364
9426
  return "A workspace on the free plan can't share credits. Upgrade this workspace's plan first.";
9365
- case "ALLOCATION_NOT_ALLOWED":
9366
- return found.message ? found.message.replace(/\.?$/, ".") : "This allocation isn't allowed.";
9367
- case "TARGET_PLAN_CHANGE_FAILED":
9368
- 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.";
9427
+ case "POOL_NOT_ALLOWED":
9428
+ return found.message ? found.message.replace(/\.?$/, ".") : "Credits can't be shared with that workspace.";
9369
9429
  }
9370
9430
  }
9371
9431
 
9372
- // src/credit-allocation-modal.tsx
9432
+ // src/credit-pool-modal.tsx
9373
9433
  import { Fragment as Fragment9, jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
9374
9434
  var MODAL_T = {
9375
9435
  primary: "var(--alias-text---icons-primary)",
@@ -9402,7 +9462,7 @@ var modalBodyText = {
9402
9462
  fontSize: T16.fsSm,
9403
9463
  lineHeight: T16.lhSm
9404
9464
  };
9405
- function AllocationModalShell({
9465
+ function PoolModalShell({
9406
9466
  title,
9407
9467
  busy,
9408
9468
  onClose,
@@ -9503,7 +9563,7 @@ function AllocationModalShell({
9503
9563
  }
9504
9564
  );
9505
9565
  }
9506
- function AllocationWarning({ children }) {
9566
+ function PoolWarning({ children }) {
9507
9567
  return /* @__PURE__ */ jsx25(
9508
9568
  "div",
9509
9569
  {
@@ -9522,68 +9582,55 @@ function AllocationWarning({ children }) {
9522
9582
  );
9523
9583
  }
9524
9584
  var fieldLabel = { ...modalBodyText, fontWeight: 500, color: T16.primary };
9525
- function CreditAllocationModal(props) {
9585
+ function CreditPoolMemberModal(props) {
9526
9586
  const { toast } = useSettingsAdapters();
9527
- const editing = props.kind === "edit" ? props.allocation : null;
9528
- const [targetId, setTargetId] = useState17("");
9529
- const [mode, setMode] = useState17(editing?.mode ?? "fixed");
9587
+ const editing = props.kind === "edit" ? props.member : null;
9588
+ const [memberId, setMemberId] = useState17("");
9589
+ const [mode, setMode] = useState17(editing?.mode ?? "percent");
9530
9590
  const [raw, setRaw] = useState17(editing ? String(editing.value) : "");
9531
- const [recurring, setRecurring] = useState17(editing?.recurring ?? true);
9532
9591
  const [note, setNote] = useState17("");
9533
9592
  const [touched, setTouched] = useState17(false);
9534
9593
  const [saving, setSaving] = useState17(false);
9535
9594
  const [serverError, setServerError] = useState17(null);
9536
9595
  useEffect15(() => {
9537
9596
  setServerError(null);
9538
- }, [targetId, mode, raw, recurring, note]);
9539
- const balance = props.kind === "create" ? props.balance : null;
9540
- const value = validateAllocationValue(mode, raw, props.kind === "create" ? balance : null);
9541
- const noteCheck = validateAllocationText(note, ALLOCATION_NOTE_MAX_LENGTH);
9542
- const splitChanged = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9543
- const changed = !editing || splitChanged || recurring !== editing.recurring;
9544
- const valid = value.ok && (props.kind === "edit" || Boolean(targetId) && noteCheck.ok);
9597
+ }, [memberId, mode, raw, note]);
9598
+ const value = validatePoolValue(mode, raw);
9599
+ const noteCheck = validatePoolText(note, POOL_NOTE_MAX_LENGTH);
9600
+ const changed = !editing || mode !== editing.mode || !value.ok || value.value !== editing.value;
9601
+ const valid = value.ok && (props.kind === "edit" || Boolean(memberId) && noteCheck.ok);
9545
9602
  const canSave = !saving && changed && valid;
9546
9603
  const valueHint = touched && !value.ok ? value.reason : null;
9547
- const percentNow = props.kind === "create" && mode === "percent" && value.ok && balance !== null ? Math.floor(roundCredits(balance * value.value / 100) * 100) / 100 : null;
9548
9604
  const handleSave = async () => {
9549
9605
  if (!canSave || !value.ok) return;
9550
9606
  setSaving(true);
9551
9607
  try {
9552
- if (props.kind === "create") {
9553
- const created = await props.onSave({
9554
- targetWorkspaceId: targetId,
9608
+ if (props.kind === "add") {
9609
+ const added = await props.onSave({
9610
+ memberWorkspaceId: memberId,
9555
9611
  mode,
9556
9612
  value: value.value,
9557
- recurring,
9558
9613
  note: noteCheck.ok ? noteCheck.value : ""
9559
9614
  });
9560
- const warning = initialTransferWarning(created);
9561
- if (warning) toast.error(warning);
9562
- else {
9563
- const sent = initialTransferSummary(created);
9564
- toast.success(sent ? `Allocation created. ${sent}.` : "Allocation created");
9565
- }
9615
+ toast.success(`Credits shared. ${describeAllowance(added)}.`);
9566
9616
  } else {
9567
- await props.onSave(
9568
- splitChanged ? { mode, value: value.value, recurring } : { recurring }
9569
- );
9570
- toast.success("Allocation updated. Applies from the next renewal.");
9617
+ const updated = await props.onSave({ mode, value: value.value });
9618
+ toast.success(`Share updated. ${describeAllowance(updated)}.`);
9571
9619
  }
9572
9620
  props.onSaved();
9573
9621
  props.onClose();
9574
9622
  } catch (err) {
9575
- const message = allocationErrorMessage(err);
9623
+ const message = poolErrorMessage(err);
9576
9624
  if (message) setServerError(message);
9577
- else toast.error(errorMessage(err, { method: props.kind === "create" ? "POST" : "PATCH" }));
9578
- if (findAllocationError(err)?.details.allocationId) props.onSaved();
9625
+ else toast.error(errorMessage(err, { method: props.kind === "add" ? "POST" : "PATCH" }));
9579
9626
  setSaving(false);
9580
9627
  }
9581
9628
  };
9582
- const targetOptions = props.kind === "create" ? props.targets.map((target) => ({ id: target.id, label: target.name })) : [];
9629
+ const candidateOptions = props.kind === "add" ? props.candidates.map((candidate) => ({ id: candidate.id, label: candidate.name })) : [];
9583
9630
  return /* @__PURE__ */ jsxs22(
9584
- AllocationModalShell,
9631
+ PoolModalShell,
9585
9632
  {
9586
- title: props.kind === "create" ? "New credit allocation" : "Edit credit allocation",
9633
+ title: props.kind === "add" ? "Share credits" : "Edit shared credits",
9587
9634
  busy: saving,
9588
9635
  onClose: props.onClose,
9589
9636
  footer: /* @__PURE__ */ jsxs22(Fragment9, { children: [
@@ -9600,7 +9647,7 @@ function CreditAllocationModal(props) {
9600
9647
  /* @__PURE__ */ jsx25(
9601
9648
  ScButton10,
9602
9649
  {
9603
- text: props.kind === "create" ? "Create allocation" : "Save",
9650
+ text: props.kind === "add" ? "Share credits" : "Save",
9604
9651
  variant: "primary",
9605
9652
  size: "md",
9606
9653
  state: canSave ? "default" : "disabled",
@@ -9610,29 +9657,29 @@ function CreditAllocationModal(props) {
9610
9657
  ] }),
9611
9658
  children: [
9612
9659
  /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9613
- /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Send credits to" }),
9614
- props.kind === "create" ? targetOptions.length ? /* @__PURE__ */ jsx25(
9660
+ /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Share with" }),
9661
+ props.kind === "add" ? candidateOptions.length ? /* @__PURE__ */ jsx25(
9615
9662
  ScSelect,
9616
9663
  {
9617
- value: targetId,
9618
- onChange: setTargetId,
9619
- options: targetOptions,
9664
+ value: memberId,
9665
+ onChange: setMemberId,
9666
+ options: candidateOptions,
9620
9667
  placeholder: "Choose a workspace",
9621
9668
  disabled: saving
9622
9669
  }
9623
- ) : /* @__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 })
9670
+ ) : /* @__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 })
9624
9671
  ] }),
9625
9672
  /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spMd }, children: [
9626
- /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "How much" }),
9673
+ /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "How much, each billing cycle" }),
9627
9674
  /* @__PURE__ */ jsxs22("div", { className: "flex items-center", style: { gap: T16.sp3xl }, children: [
9628
9675
  /* @__PURE__ */ jsx25(
9629
9676
  ScPairtext2,
9630
9677
  {
9631
9678
  type: "radio",
9632
- content: "Fixed credits",
9633
- checked: mode === "fixed",
9679
+ content: "Percentage",
9680
+ checked: mode === "percent",
9634
9681
  onChange: () => {
9635
- if (!saving) setMode("fixed");
9682
+ if (!saving) setMode("percent");
9636
9683
  }
9637
9684
  }
9638
9685
  ),
@@ -9640,10 +9687,10 @@ function CreditAllocationModal(props) {
9640
9687
  ScPairtext2,
9641
9688
  {
9642
9689
  type: "radio",
9643
- content: "Percentage",
9644
- checked: mode === "percent",
9690
+ content: "Fixed credits",
9691
+ checked: mode === "fixed",
9645
9692
  onChange: () => {
9646
- if (!saving) setMode("percent");
9693
+ if (!saving) setMode("fixed");
9647
9694
  }
9648
9695
  }
9649
9696
  )
@@ -9671,7 +9718,7 @@ function CreditAllocationModal(props) {
9671
9718
  value: raw,
9672
9719
  disabled: saving,
9673
9720
  onChange: (e) => {
9674
- setRaw(cleanAllocationValueInput(e.target.value));
9721
+ setRaw(cleanPoolValueInput(e.target.value));
9675
9722
  setTouched(true);
9676
9723
  },
9677
9724
  className: "flex-1 min-w-0",
@@ -9689,39 +9736,18 @@ function CreditAllocationModal(props) {
9689
9736
  ]
9690
9737
  }
9691
9738
  ),
9692
- 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." })
9693
- ] }),
9694
- /* @__PURE__ */ jsxs22("div", { className: "flex items-start justify-between w-full", style: { gap: T16.sp3xl }, children: [
9695
- /* @__PURE__ */ jsxs22("div", { className: "flex flex-col min-w-0", style: { gap: T16.spXs }, children: [
9696
- /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Repeat on every renewal" }),
9697
- /* @__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." })
9698
- ] }),
9699
- /* @__PURE__ */ jsx25("span", { style: { display: "inline-flex", width: 41, height: 25, alignItems: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx25(
9700
- ScToggleSwitch,
9701
- {
9702
- checked: recurring,
9703
- disabled: saving,
9704
- onToggle: (next) => setRecurring(next),
9705
- role: "switch",
9706
- "aria-checked": recurring,
9707
- "aria-label": "Repeat on every renewal",
9708
- tabIndex: 0,
9709
- onKeyDown: (event) => {
9710
- if ((event.key === " " || event.key === "Enter") && !saving) {
9711
- event.preventDefault();
9712
- setRecurring(!recurring);
9713
- }
9714
- }
9715
- }
9716
- ) })
9739
+ valueHint ? /* @__PURE__ */ jsx25("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: valueHint }) : /* @__PURE__ */ jsxs22("span", { style: { ...modalBodyText, color: T16.tertiary }, children: [
9740
+ 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.",
9741
+ editing ? " Applies immediately." : ""
9742
+ ] })
9717
9743
  ] }),
9718
- props.kind === "create" && /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9744
+ props.kind === "add" && /* @__PURE__ */ jsxs22("div", { className: "flex flex-col w-full", style: { gap: T16.spXs }, children: [
9719
9745
  /* @__PURE__ */ jsx25("span", { style: fieldLabel, children: "Note" }),
9720
9746
  /* @__PURE__ */ jsx25(
9721
9747
  ScTextArea,
9722
9748
  {
9723
9749
  value: note,
9724
- maxLength: ALLOCATION_NOTE_MAX_LENGTH,
9750
+ maxLength: POOL_NOTE_MAX_LENGTH,
9725
9751
  placeholder: "Why are these credits being shared?",
9726
9752
  disabled: saving,
9727
9753
  onChange: (e) => setNote(e.target.value),
@@ -9733,117 +9759,57 @@ function CreditAllocationModal(props) {
9733
9759
  "Required. ",
9734
9760
  note.trim().length,
9735
9761
  "/",
9736
- ALLOCATION_NOTE_MAX_LENGTH
9762
+ POOL_NOTE_MAX_LENGTH
9737
9763
  ] })
9738
9764
  ] }),
9739
- 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." }),
9765
+ props.kind === "add" && /* @__PURE__ */ jsx25(PoolWarning, { children: "While it spends shared credits, the other workspace uses only this workspace's credits, not its own." }),
9740
9766
  serverError && /* @__PURE__ */ jsx25("span", { role: "alert", style: { ...modalBodyText, color: T16.error }, children: serverError })
9741
9767
  ]
9742
9768
  }
9743
9769
  );
9744
9770
  }
9745
9771
 
9746
- // src/cancel-credit-allocation-modal.tsx
9772
+ // src/remove-pool-member-modal.tsx
9747
9773
  import { useState as useState18 } from "react";
9748
9774
  import { ScButton as ScButton11, ScTextArea as ScTextArea2 } from "@streamoid/ui";
9749
9775
  import { Fragment as Fragment10, jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
9750
- function CancelCreditAllocationModal({
9751
- allocation,
9752
- targetName,
9753
- onCancel,
9776
+ function RemovePoolMemberModal({
9777
+ member,
9778
+ memberName,
9779
+ sponsorName,
9780
+ onRemove,
9754
9781
  onClose,
9755
- onCanceled
9782
+ onRemoved
9756
9783
  }) {
9757
9784
  const { toast } = useSettingsAdapters();
9758
9785
  const [reason, setReason] = useState18("");
9759
9786
  const [saving, setSaving] = useState18(false);
9760
- const [outcome, setOutcome] = useState18(null);
9761
- const check = validateAllocationText(reason, CANCEL_REASON_MAX_LENGTH);
9787
+ const check = validatePoolText(reason, REMOVE_REASON_MAX_LENGTH);
9762
9788
  const canConfirm = !saving && check.ok;
9763
9789
  const handleConfirm = async () => {
9764
9790
  if (!canConfirm || !check.ok) return;
9765
9791
  setSaving(true);
9766
9792
  try {
9767
- const result = await onCancel(check.value);
9768
- const warning = cancelOutcomeWarning(
9769
- { ...result, allocation: result.allocation ?? allocation },
9770
- targetName
9771
- );
9772
- onCanceled({ planNotRestored: Boolean(warning) });
9773
- if (warning) {
9774
- setOutcome({ warning, error: result.error });
9775
- setSaving(false);
9776
- return;
9777
- }
9778
- toast.success(cancelOutcomeSummary(result, targetName));
9793
+ await onRemove(check.value);
9794
+ toast.success(`${memberName} can no longer spend ${sponsorName}'s credits`);
9795
+ onRemoved();
9779
9796
  onClose();
9780
9797
  } catch (err) {
9781
9798
  toast.error(errorMessage(err, { method: "DELETE" }));
9782
9799
  setSaving(false);
9783
9800
  }
9784
9801
  };
9785
- const copyId = () => {
9786
- const id = allocation.targetWorkspaceKey;
9787
- const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
9788
- if (!clipboard) {
9789
- toast.error("Couldn't copy. Select the ID and copy it instead.");
9790
- return;
9791
- }
9792
- clipboard.writeText(id).then(
9793
- () => toast.success("Workspace ID copied"),
9794
- () => toast.error("Couldn't copy. Select the ID and copy it instead.")
9795
- );
9796
- };
9797
- if (outcome) {
9798
- return /* @__PURE__ */ jsxs23(
9799
- AllocationModalShell,
9800
- {
9801
- title: "Target plan not restored",
9802
- busy: false,
9803
- onClose,
9804
- footer: /* @__PURE__ */ jsx26(ScButton11, { text: "Got it", variant: "primary", size: "md", onClick: onClose }),
9805
- children: [
9806
- /* @__PURE__ */ jsx26(AllocationWarning, { children: outcome.warning }),
9807
- /* @__PURE__ */ jsxs23("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9808
- /* @__PURE__ */ jsx26("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Workspace ID" }),
9809
- /* @__PURE__ */ jsxs23("div", { className: "flex items-center w-full", style: { gap: MODAL_T.spXl }, children: [
9810
- /* @__PURE__ */ jsx26(
9811
- "code",
9812
- {
9813
- className: "flex-1 min-w-0 truncate",
9814
- style: {
9815
- ...modalBodyText,
9816
- color: MODAL_T.primary,
9817
- background: MODAL_T.fillPlus,
9818
- borderRadius: MODAL_T.r3xl,
9819
- padding: `${MODAL_T.spXl} ${MODAL_T.sp3xl}`,
9820
- userSelect: "all"
9821
- },
9822
- children: allocation.targetWorkspaceKey
9823
- }
9824
- ),
9825
- /* @__PURE__ */ jsx26(ScButton11, { text: "Copy", variant: "secondary", size: "sm", onClick: copyId })
9826
- ] })
9827
- ] }),
9828
- outcome.error && /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: MODAL_T.muted }, children: [
9829
- "Details: ",
9830
- outcome.error
9831
- ] })
9832
- ]
9833
- }
9834
- );
9835
- }
9836
9802
  return /* @__PURE__ */ jsxs23(
9837
- AllocationModalShell,
9803
+ PoolModalShell,
9838
9804
  {
9839
- title: "Cancel credit allocation",
9805
+ title: "Stop sharing credits",
9840
9806
  busy: saving,
9841
9807
  onClose,
9842
9808
  footer: /* @__PURE__ */ jsxs23(Fragment10, { children: [
9843
9809
  /* @__PURE__ */ jsx26(
9844
9810
  ScButton11,
9845
9811
  {
9846
- text: "Keep allocation",
9812
+ text: "Keep sharing",
9847
9813
  variant: "secondary",
9848
9814
  size: "md",
9849
9815
  state: saving ? "disabled" : "default",
@@ -9853,7 +9819,7 @@ function CancelCreditAllocationModal({
9853
9819
  /* @__PURE__ */ jsx26(
9854
9820
  ScButton11,
9855
9821
  {
9856
- text: "Cancel allocation",
9822
+ text: "Stop sharing",
9857
9823
  variant: "error",
9858
9824
  size: "md",
9859
9825
  state: canConfirm ? "default" : "disabled",
@@ -9863,16 +9829,18 @@ function CancelCreditAllocationModal({
9863
9829
  ] }),
9864
9830
  children: [
9865
9831
  /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: MODAL_T.primary }, children: [
9866
- "Stop sending ",
9867
- describeAllocation(allocation),
9868
- " to ",
9869
- targetName,
9870
- "?"
9832
+ "This stops ",
9833
+ memberName,
9834
+ " spending ",
9835
+ sponsorName,
9836
+ "'s credits immediately."
9871
9837
  ] }),
9872
9838
  /* @__PURE__ */ jsxs23("span", { style: { ...modalBodyText, color: MODAL_T.tertiary }, children: [
9873
- targetName,
9874
- " keeps the credits it already received until they expire.",
9875
- 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." : ""
9839
+ "It had ",
9840
+ describeAllowance(member),
9841
+ " and has used ",
9842
+ formatCredits(member.used),
9843
+ ". Credits it already spent are not returned; anything it runs from now uses its own credits."
9876
9844
  ] }),
9877
9845
  /* @__PURE__ */ jsxs23("div", { className: "flex flex-col w-full", style: { gap: MODAL_T.spXs }, children: [
9878
9846
  /* @__PURE__ */ jsx26("span", { style: { ...modalBodyText, fontWeight: 500, color: MODAL_T.primary }, children: "Reason" }),
@@ -9880,8 +9848,8 @@ function CancelCreditAllocationModal({
9880
9848
  ScTextArea2,
9881
9849
  {
9882
9850
  value: reason,
9883
- maxLength: CANCEL_REASON_MAX_LENGTH,
9884
- placeholder: "Why is this allocation ending?",
9851
+ maxLength: REMOVE_REASON_MAX_LENGTH,
9852
+ placeholder: "Why is this workspace no longer sharing these credits?",
9885
9853
  disabled: saving,
9886
9854
  onChange: (e) => setReason(e.target.value),
9887
9855
  rows: 3,
@@ -9895,16 +9863,13 @@ function CancelCreditAllocationModal({
9895
9863
  );
9896
9864
  }
9897
9865
 
9898
- // src/credit-allocations-view.tsx
9866
+ // src/shared-credits-view.tsx
9899
9867
  import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
9900
9868
  var T17 = {
9901
9869
  primary: "var(--alias-text---icons-primary)",
9902
9870
  muted: "var(--alias-text---icons-muted)",
9903
9871
  tertiary: "var(--alias-text---icons-tertiary)",
9904
9872
  warning: "var(--alias-text---icons-warning)",
9905
- success: "var(--alias-text---icons-success)",
9906
- warningFill: "var(--alias-fill-warning-soft)",
9907
- warningBorder: "var(--alias-border-warning)",
9908
9873
  fillPlus: "var(--alias-fill-neutral-neutralplus)",
9909
9874
  surfaceBase: "var(--alias-surface-base)",
9910
9875
  borderSubtle: "var(--alias-border-subtle)",
@@ -9918,9 +9883,7 @@ var T17 = {
9918
9883
  r3xl: "var(--radius-3xl)",
9919
9884
  rFull: "var(--radius-full)"
9920
9885
  };
9921
- var GIVING_COLUMNS = "minmax(220px, 1fr) 120px 120px 150px 110px 150px";
9922
- var TRANSFER_COLUMNS = "120px 170px minmax(200px, 1fr) 120px";
9923
- var TRANSFERS_PAGE = 20;
9886
+ var MEMBER_COLUMNS2 = "minmax(220px, 1fr) 220px 110px 110px 150px";
9924
9887
  var cellText2 = {
9925
9888
  fontFamily: T17.font,
9926
9889
  fontSize: T17.fsSm,
@@ -9942,138 +9905,67 @@ var tableHeader = (columns, labels) => /* @__PURE__ */ jsx27(
9942
9905
  children: labels.map((label) => /* @__PURE__ */ jsx27("span", { children: label }, label))
9943
9906
  }
9944
9907
  );
9945
- function Badge({ text: text2, tone }) {
9946
- return /* @__PURE__ */ jsx27(
9947
- "span",
9948
- {
9949
- className: "inline-flex items-center shrink-0",
9950
- style: {
9951
- ...cellText2,
9952
- fontSize: 12,
9953
- padding: "0 8px",
9954
- height: 22,
9955
- borderRadius: T17.rFull,
9956
- color: tone === "warning" ? T17.primary : T17.muted,
9957
- background: tone === "warning" ? T17.warningFill : T17.fillPlus,
9958
- border: tone === "warning" ? `1px solid ${T17.warningBorder}` : "1px solid transparent"
9959
- },
9960
- children: text2
9961
- }
9962
- );
9963
- }
9964
- function GivingRow({
9965
- allocation,
9966
- planNotRestored,
9908
+ function MemberRow2({
9909
+ member,
9967
9910
  onEdit,
9968
- onCancel
9911
+ onRemove
9969
9912
  }) {
9970
- const failed = initialTransferWarning(allocation);
9971
- const sent = initialTransferSummary(allocation);
9972
- const active = allocation.status === "active";
9913
+ const spent = member.remaining <= 0;
9973
9914
  return /* @__PURE__ */ jsxs24(
9974
9915
  "div",
9975
9916
  {
9976
9917
  style: {
9977
9918
  borderBottom: `1px solid ${T17.borderSubtle}`,
9978
9919
  display: "grid",
9979
- gridTemplateColumns: GIVING_COLUMNS,
9920
+ gridTemplateColumns: MEMBER_COLUMNS2,
9980
9921
  alignItems: "center",
9981
9922
  padding: `10px ${T17.sp3xl}`,
9982
9923
  gap: 24
9983
9924
  },
9984
9925
  children: [
9985
9926
  /* @__PURE__ */ jsxs24("div", { className: "flex flex-col min-w-0", style: { gap: 2 }, children: [
9986
- /* @__PURE__ */ jsx27("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: allocationWorkspaceName(allocation.targetWorkspaceName) }),
9987
- failed ? /* @__PURE__ */ jsxs24("div", { className: "flex flex-col", style: { gap: 2 }, children: [
9988
- /* @__PURE__ */ jsx27("span", { children: /* @__PURE__ */ jsx27(Badge, { text: "First transfer failed", tone: "warning" }) }),
9989
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: failed })
9990
- ] }) : sent ? /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: sent }) : null,
9991
- allocation.note && /* @__PURE__ */ jsx27("span", { className: "truncate", title: allocation.note, style: { ...cellText2, color: T17.tertiary }, children: allocation.note })
9992
- ] }),
9993
- /* @__PURE__ */ jsx27("span", { style: cellText2, children: formatAllocationSplit(allocation) }),
9994
- /* @__PURE__ */ jsx27("span", { style: cellText2, children: formatAllocationRepeat(allocation.recurring) }),
9995
- /* @__PURE__ */ jsxs24("div", { className: "flex flex-wrap items-center", style: { gap: 6 }, children: [
9996
- /* @__PURE__ */ jsx27(Badge, { text: active ? "Active" : "Canceled", tone: "neutral" }),
9997
- planNotRestored && /* @__PURE__ */ jsx27(Badge, { text: "Plan not restored", tone: "warning" })
9927
+ /* @__PURE__ */ jsx27("span", { className: "truncate", style: { ...cellText2, fontWeight: 500 }, children: poolWorkspaceName(member.memberWorkspaceName) }),
9928
+ member.note && /* @__PURE__ */ jsx27("span", { className: "truncate", title: member.note, style: { ...cellText2, color: T17.tertiary }, children: member.note })
9998
9929
  ] }),
9999
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(allocation.createdAt) }),
9930
+ /* @__PURE__ */ jsx27("span", { style: cellText2, children: describeAllowance(member) }),
9931
+ /* @__PURE__ */ jsx27("span", { style: cellText2, children: formatCredits(member.used) }),
9932
+ /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: spent ? T17.warning : T17.primary }, children: formatCredits(member.remaining) }),
10000
9933
  /* @__PURE__ */ jsxs24("div", { className: "flex items-center", style: { gap: 8 }, children: [
10001
- active && onEdit && /* @__PURE__ */ jsx27(ScButton12, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
10002
- active && onCancel && /* @__PURE__ */ jsx27(ScButton12, { text: "Cancel", variant: "secondary", size: "sm", onClick: onCancel })
10003
- ] })
10004
- ]
10005
- }
10006
- );
10007
- }
10008
- function TransferRow({ transfer }) {
10009
- return /* @__PURE__ */ jsxs24(
10010
- "div",
10011
- {
10012
- style: {
10013
- borderBottom: `1px solid ${T17.borderSubtle}`,
10014
- display: "grid",
10015
- gridTemplateColumns: TRANSFER_COLUMNS,
10016
- alignItems: "center",
10017
- padding: `8px ${T17.sp3xl}`,
10018
- gap: 24
10019
- },
10020
- children: [
10021
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, color: T17.muted }, children: formatDate2(transfer.createdAt) }),
10022
- /* @__PURE__ */ jsxs24("span", { style: cellText2, children: [
10023
- transfer.direction === "sent" ? "Sent" : "Received",
10024
- " \xB7",
10025
- " ",
10026
- transfer.kind === "initial" ? "Initial" : "Renewal"
10027
- ] }),
10028
- /* @__PURE__ */ jsx27("span", { className: "truncate", style: cellText2, children: transfer.counterpartWorkspaceName ?? (transfer.counterpartWorkspaceId ? "Unknown workspace" : "\u2014") }),
10029
- /* @__PURE__ */ jsxs24("span", { style: { ...cellText2, color: transfer.direction === "received" ? T17.success : T17.primary }, children: [
10030
- transfer.direction === "received" ? "+" : "\u2212",
10031
- formatCredits(transfer.credits)
9934
+ onEdit && /* @__PURE__ */ jsx27(ScButton12, { text: "Edit", variant: "secondary", size: "sm", onClick: onEdit }),
9935
+ onRemove && /* @__PURE__ */ jsx27(ScButton12, { text: "Remove", variant: "secondary", size: "sm", onClick: onRemove })
10032
9936
  ] })
10033
9937
  ]
10034
9938
  }
10035
9939
  );
10036
9940
  }
10037
- function CreditAllocationsView() {
9941
+ function SharedCreditsView() {
10038
9942
  const { api, workspace, toast } = useSettingsAdapters();
10039
9943
  const workspaceId = workspace.current?.id ?? null;
9944
+ const workspaceName = workspace.current?.name?.trim() || "this workspace";
10040
9945
  const [data, setData] = useState19(null);
10041
9946
  const [loading, setLoading] = useState19(true);
10042
9947
  const [failed, setFailed] = useState19(false);
10043
- const [targets, setTargets] = useState19([]);
10044
- const [transfers, setTransfers] = useState19([]);
10045
- const [transfersTotal, setTransfersTotal] = useState19(0);
10046
- const [transfersPage, setTransfersPage] = useState19(0);
10047
- const [transfersFailed, setTransfersFailed] = useState19(false);
10048
- const [loadingMore, setLoadingMore] = useState19(false);
10049
- const [creating, setCreating] = useState19(false);
9948
+ const [candidates, setCandidates] = useState19([]);
9949
+ const [adding, setAdding] = useState19(false);
10050
9950
  const [editing, setEditing] = useState19(null);
10051
- const [canceling, setCanceling] = useState19(null);
10052
- const [planNotRestored, setPlanNotRestored] = useState19(() => /* @__PURE__ */ new Set());
9951
+ const [removing, setRemoving] = useState19(null);
10053
9952
  const latestLoad = useRef8(0);
10054
9953
  const load = useCallback7(
10055
9954
  async ({ refresh = false } = {}) => {
10056
9955
  const loadId = ++latestLoad.current;
10057
- if (!api.fetchCreditAllocations) {
9956
+ if (!api.fetchCreditPool) {
10058
9957
  setLoading(false);
10059
9958
  setFailed(true);
10060
9959
  return;
10061
9960
  }
10062
- const [next, page] = await Promise.all([
10063
- api.fetchCreditAllocations().catch(() => null),
10064
- api.fetchCreditTransfers ? api.fetchCreditTransfers({ page: 1, limit: TRANSFERS_PAGE }).catch(() => null) : Promise.resolve(null)
10065
- ]);
9961
+ const next = await api.fetchCreditPool().catch(() => null);
10066
9962
  if (loadId !== latestLoad.current) return;
10067
9963
  if (refresh && !next) {
10068
- toast.error("Couldn't refresh credit allocations. Reopen this page to see the latest.");
9964
+ toast.error("Couldn't refresh shared credits. Reopen this page to see the latest.");
10069
9965
  return;
10070
9966
  }
10071
9967
  setFailed(!next);
10072
9968
  setData(next);
10073
- setTransfersFailed(Boolean(api.fetchCreditTransfers) && !page);
10074
- setTransfers(page?.transfers ?? []);
10075
- setTransfersTotal(page?.total ?? 0);
10076
- setTransfersPage(page ? 1 : 0);
10077
9969
  setLoading(false);
10078
9970
  },
10079
9971
  [api, toast]
@@ -10082,11 +9974,9 @@ function CreditAllocationsView() {
10082
9974
  setLoading(true);
10083
9975
  setFailed(false);
10084
9976
  setData(null);
10085
- setTransfers([]);
10086
- setCreating(false);
9977
+ setAdding(false);
10087
9978
  setEditing(null);
10088
- setCanceling(null);
10089
- setPlanNotRestored(/* @__PURE__ */ new Set());
9979
+ setRemoving(null);
10090
9980
  void load();
10091
9981
  }, [workspaceId]);
10092
9982
  useEffect16(
@@ -10096,36 +9986,21 @@ function CreditAllocationsView() {
10096
9986
  []
10097
9987
  );
10098
9988
  const canManage = Boolean(data?.canManage);
10099
- const canCreate = canManage && Boolean(api.createCreditAllocation);
10100
- const openCreate = async () => {
10101
- if (!api.fetchAllocationTargets) {
10102
- setTargets([]);
10103
- setCreating(true);
9989
+ const sponsor = data?.sponsor ?? null;
9990
+ const canAdd = canManage && !sponsor && Boolean(api.addCreditPoolMember);
9991
+ const openAdd = async () => {
9992
+ if (!api.fetchCreditPoolCandidates) {
9993
+ setCandidates([]);
9994
+ setAdding(true);
10104
9995
  return;
10105
9996
  }
10106
9997
  try {
10107
- setTargets(await api.fetchAllocationTargets());
10108
- setCreating(true);
9998
+ setCandidates(await api.fetchCreditPoolCandidates());
9999
+ setAdding(true);
10109
10000
  } catch {
10110
10001
  toast.error("Couldn't load this organization's workspaces. Try again.");
10111
10002
  }
10112
10003
  };
10113
- const loadMoreTransfers = async () => {
10114
- if (!api.fetchCreditTransfers || loadingMore) return;
10115
- const loadId = latestLoad.current;
10116
- setLoadingMore(true);
10117
- try {
10118
- const page = await api.fetchCreditTransfers({ page: transfersPage + 1, limit: TRANSFERS_PAGE });
10119
- if (loadId !== latestLoad.current) return;
10120
- setTransfers((prev) => [...prev, ...page.transfers]);
10121
- setTransfersTotal(page.total);
10122
- setTransfersPage(transfersPage + 1);
10123
- } catch {
10124
- toast.error("Couldn't load more transfers. Try again.");
10125
- } finally {
10126
- setLoadingMore(false);
10127
- }
10128
- };
10129
10004
  if (loading) {
10130
10005
  return /* @__PURE__ */ jsx27(
10131
10006
  "div",
@@ -10148,9 +10023,7 @@ function CreditAllocationsView() {
10148
10023
  }
10149
10024
  );
10150
10025
  }
10151
- const giving = data?.giving ?? [];
10152
- const sponsor = data?.receiving.find((a) => a.status === "active") ?? null;
10153
- const balance = typeof workspace.current?.availableCredits === "number" ? workspace.current.availableCredits : null;
10026
+ const members = data?.members ?? [];
10154
10027
  return /* @__PURE__ */ jsxs24("div", { className: "flex flex-col flex-1 min-h-0 w-full overflow-y-auto", style: { gap: 16 }, children: [
10155
10028
  /* @__PURE__ */ jsxs24("div", { className: "flex items-start justify-between shrink-0 w-full", style: { gap: 12 }, children: [
10156
10029
  /* @__PURE__ */ jsxs24("div", { className: "flex flex-col min-w-0", style: { gap: 4 }, children: [
@@ -10158,12 +10031,12 @@ function CreditAllocationsView() {
10158
10031
  "span",
10159
10032
  {
10160
10033
  style: { fontFamily: T17.font, fontSize: T17.fsXl, fontWeight: 500, lineHeight: T17.lhXl, color: T17.primary },
10161
- children: "Credit allocations"
10034
+ children: "Shared credits"
10162
10035
  }
10163
10036
  ),
10164
- /* @__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." })
10037
+ /* @__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." })
10165
10038
  ] }),
10166
- canCreate && !failed && /* @__PURE__ */ jsx27(ScButton12, { text: "New allocation", variant: "primary", size: "md", onClick: () => void openCreate() })
10039
+ canAdd && !failed && /* @__PURE__ */ jsx27(ScButton12, { text: "Share credits", variant: "primary", size: "md", onClick: () => void openAdd() })
10167
10040
  ] }),
10168
10041
  sponsor && /* @__PURE__ */ jsxs24(
10169
10042
  "div",
@@ -10177,103 +10050,74 @@ function CreditAllocationsView() {
10177
10050
  borderRadius: T17.r3xl
10178
10051
  },
10179
10052
  children: [
10180
- /* @__PURE__ */ jsxs24("span", { style: { ...cellText2, fontWeight: 500 }, children: [
10181
- "Funded by ",
10182
- allocationWorkspaceName(sponsor.sourceWorkspaceName)
10183
- ] }),
10053
+ /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: sharedPoolBannerText({
10054
+ sponsorName: poolWorkspaceName(sponsor.sponsorWorkspaceName),
10055
+ allowance: sponsor.allowance,
10056
+ remaining: sponsor.remaining
10057
+ }) }),
10184
10058
  /* @__PURE__ */ jsxs24("span", { style: { ...cellText2, color: T17.muted }, children: [
10185
- "Receives ",
10186
- describeAllocation(sponsor),
10059
+ "This workspace spends ",
10060
+ poolWorkspaceName(sponsor.sponsorWorkspaceName),
10061
+ "'s credits, not its own",
10062
+ sponsor.cycle?.end ? `, until its allowance starts again on ${formatDate2(sponsor.cycle.end)}` : "",
10187
10063
  "."
10188
10064
  ] })
10189
10065
  ]
10190
10066
  }
10191
10067
  ),
10192
- /* @__PURE__ */ jsxs24("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10193
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: "Giving" }),
10068
+ !sponsor && /* @__PURE__ */ jsxs24("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10069
+ /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: "Shared with" }),
10194
10070
  /* @__PURE__ */ jsx27(
10195
10071
  MemberListFrame,
10196
10072
  {
10197
10073
  grow: "cap",
10198
10074
  maxHeight: 420,
10199
- contentMinWidth: 940,
10200
- header: tableHeader(GIVING_COLUMNS, ["Workspace", "Split", "Repeats", "Status", "Created", "Actions"]),
10201
- isEmpty: failed || giving.length === 0,
10202
- emptyText: failed ? "Couldn't load credit allocations. Try again later." : "This workspace doesn't send credits to any other workspace.",
10203
- children: giving.map((allocation) => /* @__PURE__ */ jsx27(
10204
- GivingRow,
10075
+ contentMinWidth: 860,
10076
+ header: tableHeader(MEMBER_COLUMNS2, ["Workspace", "Allowance", "Used", "Remaining", "Actions"]),
10077
+ isEmpty: failed || members.length === 0,
10078
+ emptyText: failed ? "Couldn't load shared credits. Try again later." : "This workspace doesn't share its credits with any other workspace.",
10079
+ children: members.map((member) => /* @__PURE__ */ jsx27(
10080
+ MemberRow2,
10205
10081
  {
10206
- allocation,
10207
- planNotRestored: planNotRestored.has(allocation._id),
10208
- onEdit: canManage && api.updateCreditAllocation ? () => setEditing(allocation) : void 0,
10209
- onCancel: canManage && api.cancelCreditAllocation ? () => setCanceling(allocation) : void 0
10082
+ member,
10083
+ onEdit: canManage && api.updateCreditPoolMember ? () => setEditing(member) : void 0,
10084
+ onRemove: canManage && api.removeCreditPoolMember ? () => setRemoving(member) : void 0
10210
10085
  },
10211
- allocation._id
10086
+ member._id
10212
10087
  ))
10213
10088
  }
10214
10089
  )
10215
10090
  ] }),
10216
- api.fetchCreditTransfers && !failed && /* @__PURE__ */ jsxs24("div", { className: "flex flex-col shrink-0 w-full", style: { gap: 8 }, children: [
10217
- /* @__PURE__ */ jsx27("span", { style: { ...cellText2, fontWeight: 500 }, children: "Transfer history" }),
10218
- /* @__PURE__ */ jsx27(
10219
- MemberListFrame,
10220
- {
10221
- grow: "cap",
10222
- maxHeight: 420,
10223
- contentMinWidth: 680,
10224
- header: tableHeader(TRANSFER_COLUMNS, ["Date", "Type", "Workspace", "Credits"]),
10225
- isEmpty: transfersFailed || transfers.length === 0,
10226
- emptyText: transfersFailed ? "Couldn't load transfers. Try again later." : "No transfers yet",
10227
- children: transfers.map((transfer) => /* @__PURE__ */ jsx27(TransferRow, { transfer }, transfer.id))
10228
- }
10229
- ),
10230
- transfers.length < transfersTotal && /* @__PURE__ */ jsx27("div", { children: /* @__PURE__ */ jsx27(
10231
- ScButton12,
10232
- {
10233
- text: loadingMore ? "Loading\u2026" : "Load more",
10234
- variant: "secondary",
10235
- size: "sm",
10236
- state: loadingMore ? "disabled" : "default",
10237
- onClick: loadingMore ? void 0 : () => void loadMoreTransfers()
10238
- }
10239
- ) })
10240
- ] }),
10241
- creating && api.createCreditAllocation && /* @__PURE__ */ jsx27(
10242
- CreditAllocationModal,
10091
+ adding && api.addCreditPoolMember && /* @__PURE__ */ jsx27(
10092
+ CreditPoolMemberModal,
10243
10093
  {
10244
- kind: "create",
10245
- targets,
10246
- balance,
10247
- onSave: (input) => api.createCreditAllocation(input),
10248
- onClose: () => setCreating(false),
10094
+ kind: "add",
10095
+ candidates,
10096
+ onSave: (input) => api.addCreditPoolMember(input),
10097
+ onClose: () => setAdding(false),
10249
10098
  onSaved: () => void load({ refresh: true })
10250
10099
  }
10251
10100
  ),
10252
- editing && api.updateCreditAllocation && /* @__PURE__ */ jsx27(
10253
- CreditAllocationModal,
10101
+ editing && api.updateCreditPoolMember && /* @__PURE__ */ jsx27(
10102
+ CreditPoolMemberModal,
10254
10103
  {
10255
10104
  kind: "edit",
10256
- allocation: editing,
10257
- targetName: allocationWorkspaceName(editing.targetWorkspaceName),
10258
- onSave: (patch) => api.updateCreditAllocation(editing._id, patch),
10105
+ member: editing,
10106
+ memberName: poolWorkspaceName(editing.memberWorkspaceName),
10107
+ onSave: (patch) => api.updateCreditPoolMember(editing._id, patch),
10259
10108
  onClose: () => setEditing(null),
10260
10109
  onSaved: () => void load({ refresh: true })
10261
10110
  }
10262
10111
  ),
10263
- canceling && api.cancelCreditAllocation && /* @__PURE__ */ jsx27(
10264
- CancelCreditAllocationModal,
10112
+ removing && api.removeCreditPoolMember && /* @__PURE__ */ jsx27(
10113
+ RemovePoolMemberModal,
10265
10114
  {
10266
- allocation: canceling,
10267
- targetName: allocationWorkspaceName(canceling.targetWorkspaceName),
10268
- onCancel: (reason) => api.cancelCreditAllocation(canceling._id, reason),
10269
- onClose: () => setCanceling(null),
10270
- onCanceled: ({ planNotRestored: stuck }) => {
10271
- if (stuck) {
10272
- const id = canceling._id;
10273
- setPlanNotRestored((prev) => new Set(prev).add(id));
10274
- }
10275
- void load({ refresh: true });
10276
- }
10115
+ member: removing,
10116
+ memberName: poolWorkspaceName(removing.memberWorkspaceName),
10117
+ sponsorName: workspaceName,
10118
+ onRemove: (reason) => api.removeCreditPoolMember(removing._id, reason),
10119
+ onClose: () => setRemoving(null),
10120
+ onRemoved: () => void load({ refresh: true })
10277
10121
  }
10278
10122
  )
10279
10123
  ] });
@@ -11163,8 +11007,11 @@ function InvoiceHistoryTable({
11163
11007
  function UsageLogsTable({
11164
11008
  logs,
11165
11009
  userMap,
11166
- loading
11010
+ loading,
11011
+ currentWorkspaceId = null,
11012
+ usedByNames = {}
11167
11013
  }) {
11014
+ const showWorkspace = logs.some((log) => usedByWorkspaceKey(log, currentWorkspaceId));
11168
11015
  if (loading) {
11169
11016
  return /* @__PURE__ */ jsx28(
11170
11017
  "div",
@@ -11241,6 +11088,14 @@ function UsageLogsTable({
11241
11088
  style: { flexShrink: 0, width: "7.5rem" }
11242
11089
  }
11243
11090
  ),
11091
+ showWorkspace && /* @__PURE__ */ jsx28(
11092
+ ScHeader,
11093
+ {
11094
+ text: "Workspace",
11095
+ state: "none",
11096
+ style: { flexShrink: 0, width: "9rem" }
11097
+ }
11098
+ ),
11244
11099
  /* @__PURE__ */ jsx28(
11245
11100
  ScHeader,
11246
11101
  {
@@ -11287,6 +11142,8 @@ function UsageLogsTable({
11287
11142
  logs.map((log) => {
11288
11143
  const userName = log.triggeredByUserId ? userMap[log.triggeredByUserId] || "Unknown" : "System";
11289
11144
  const app = log.service?.serviceGroupName || "\u2014";
11145
+ const usedBy = usedByWorkspaceKey(log, currentWorkspaceId);
11146
+ const workspaceLabel2 = usedBy ? `Used by ${usedByNames[usedBy.toLowerCase()] ?? "another workspace"}` : "This workspace";
11290
11147
  const activity = log.type === "return" ? "Returned Credits" : log.meta?.info || "Credit usage";
11291
11148
  const isPositive = log.type === "return" || log.type === "add";
11292
11149
  const signedAmount = `${isPositive ? "+" : "-"}${log.amount}`;
@@ -11316,6 +11173,18 @@ function UsageLogsTable({
11316
11173
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, flex: 1 }, children: app }),
11317
11174
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, flex: 1 }, children: activity }),
11318
11175
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: userName }),
11176
+ showWorkspace && /* @__PURE__ */ jsx28(
11177
+ "div",
11178
+ {
11179
+ style: {
11180
+ ...rowCellRegular,
11181
+ width: "9rem",
11182
+ color: usedBy ? "var(--alias-text---icons-primary, #f5f5f5)" : "var(--alias-text---icons-muted)"
11183
+ },
11184
+ title: workspaceLabel2,
11185
+ children: workspaceLabel2
11186
+ }
11187
+ ),
11319
11188
  /* @__PURE__ */ jsx28("div", { style: { ...rowCellRegular, width: "7.5rem" }, children: formatDate3(log.createdAt) }),
11320
11189
  /* @__PURE__ */ jsx28(
11321
11190
  "div",
@@ -11384,8 +11253,8 @@ function BillingContent({
11384
11253
  onShowPlansChange,
11385
11254
  showPerUserLimits: showPerUserLimitsProp,
11386
11255
  onShowPerUserLimitsChange,
11387
- showCreditAllocations: showCreditAllocationsProp,
11388
- onShowCreditAllocationsChange
11256
+ showSharedCredits: showSharedCreditsProp,
11257
+ onShowSharedCreditsChange
11389
11258
  } = {}) {
11390
11259
  const { auth, workspace, organization, api, toast, navigation } = useSettingsAdapters();
11391
11260
  const user = auth.user;
@@ -11410,21 +11279,23 @@ function BillingContent({
11410
11279
  if (onShowPerUserLimitsChange) onShowPerUserLimitsChange(next);
11411
11280
  else setShowPerUserLimitsInternal(next);
11412
11281
  };
11413
- const canAllocateCredits = canShowCreditAllocations(
11282
+ const canShareCredits = canShowSharedCredits(
11414
11283
  api,
11415
11284
  workspace.current,
11416
11285
  organization?.current
11417
11286
  );
11418
- const [showCreditAllocationsInternal, setShowCreditAllocationsInternal] = useState20(false);
11419
- const showCreditAllocations = (showCreditAllocationsProp ?? showCreditAllocationsInternal) && canAllocateCredits;
11420
- const setShowCreditAllocations = (next) => {
11421
- if (onShowCreditAllocationsChange) onShowCreditAllocationsChange(next);
11422
- else setShowCreditAllocationsInternal(next);
11287
+ const [showSharedCreditsInternal, setShowSharedCreditsInternal] = useState20(false);
11288
+ const showSharedCredits = (showSharedCreditsProp ?? showSharedCreditsInternal) && canShareCredits;
11289
+ const setShowSharedCredits = (next) => {
11290
+ if (onShowSharedCreditsChange) onShowSharedCreditsChange(next);
11291
+ else setShowSharedCreditsInternal(next);
11423
11292
  };
11424
11293
  const [showExportModal, setShowExportModal] = useState20(false);
11425
11294
  const [showBuyCreditsModal, setShowBuyCreditsModal] = useState20(false);
11426
11295
  const [subscription, setSubscription] = useState20(null);
11427
11296
  const [availableCredits, setAvailableCredits] = useState20(null);
11297
+ const [sharedPool, setSharedPool] = useState20(null);
11298
+ const [usedByNames, setUsedByNames] = useState20({});
11428
11299
  const [products, setProducts] = useState20([]);
11429
11300
  const [invoices, setInvoices] = useState20([]);
11430
11301
  const [totalCredits, setTotalCredits] = useState20(null);
@@ -11475,6 +11346,7 @@ function BillingContent({
11475
11346
  if (subRes?.data) setSubscription(subRes.data);
11476
11347
  if (creditsRes?.availableCredits != null)
11477
11348
  setAvailableCredits(creditsRes.availableCredits);
11349
+ setSharedPool(creditsRes?.sharedPool ?? null);
11478
11350
  if (productsRes?.data) {
11479
11351
  const normalizedWorkspaceCountry = workspace.current?.country?.trim().toUpperCase();
11480
11352
  const targetCurrency = normalizedWorkspaceCountry === "IN" ? "inr" : "usd";
@@ -11523,7 +11395,12 @@ function BillingContent({
11523
11395
  );
11524
11396
  } else if (api.fetchUsageLogs) {
11525
11397
  const res = await api.fetchUsageLogs();
11526
- setUsageLogs(res.data?.length ? res.data : []);
11398
+ const logs = res.data?.length ? res.data : [];
11399
+ setUsageLogs(logs);
11400
+ if (api.fetchCreditPool && logs.some((log) => usedByWorkspaceKey(log, workspaceId))) {
11401
+ const pool = await api.fetchCreditPool().catch(() => null);
11402
+ setUsedByNames(usedByNamesFrom(pool));
11403
+ }
11527
11404
  } else {
11528
11405
  setUsageLogs([]);
11529
11406
  }
@@ -11534,7 +11411,7 @@ function BillingContent({
11534
11411
  setLoadingTable(false);
11535
11412
  }
11536
11413
  },
11537
- [api]
11414
+ [api, workspaceId]
11538
11415
  );
11539
11416
  const handlePlanAction = useCallback8(
11540
11417
  async ({ productId, priceId, planName: planName2 }) => {
@@ -11692,10 +11569,13 @@ function BillingContent({
11692
11569
  );
11693
11570
  const scheduledDowngradeText = subscription?.downgradeScheduled && subscription.plan?.name ? `Downgrades to ${subscription.plan.name}${scheduledDowngradeDateLabel ? ` ${scheduledDowngradeDateLabel}` : ""}` : void 0;
11694
11571
  const isPlanDetailsLoading = !billingOverviewLoaded || loadingBilling || subscription?.plan == null || !planName || planName === "-" || !planAmount || currentPlanCredits == null;
11695
- const usedCredits = availableCredits != null && totalCredits != null ? Math.max(0, totalCredits - availableCredits) : null;
11696
- const progressPct = usedCredits != null && totalCredits != null && totalCredits > 0 ? Math.min(100, Math.round(usedCredits / totalCredits * 100)) : 0;
11697
- const remainingPct = 100 - progressPct;
11698
- const usageText = usedCredits != null ? formatCredits2(usedCredits) : availableCredits != null ? `${formatCredits2(availableCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11572
+ const {
11573
+ used: usedCredits,
11574
+ remaining: remainingCredits,
11575
+ progressPct,
11576
+ remainingPct
11577
+ } = creditsMeter({ availableCredits, totalCredits, sharedPool });
11578
+ const usageText = usedCredits != null ? formatCredits2(usedCredits) : remainingCredits != null ? `${formatCredits2(remainingCredits)} available` : customPlanCredits != null ? `${formatCredits2(customPlanCredits)} credits` : "\u2014";
11699
11579
  const handleExport = async (startDate, endDate) => {
11700
11580
  const end = new Date(endDate);
11701
11581
  end.setHours(23, 59, 59, 999);
@@ -11772,7 +11652,7 @@ function BillingContent({
11772
11652
  function fmtFilename(d) {
11773
11653
  return d.toISOString().slice(0, 10);
11774
11654
  }
11775
- async function handleBuyCredits(credits, price) {
11655
+ async function handleBuyCredits(credits2, price) {
11776
11656
  if (!workspaceId) {
11777
11657
  toast.error("Missing workspace details for credit top-up");
11778
11658
  return;
@@ -11784,14 +11664,14 @@ function BillingContent({
11784
11664
  "Buy credits confirmed",
11785
11665
  {
11786
11666
  action: "confirm_buy_credits",
11787
- additionalInfo: `${credits}:${price}`
11667
+ additionalInfo: `${credits2}:${price}`
11788
11668
  }
11789
11669
  );
11790
11670
  try {
11791
11671
  const topupCurrency = workspace.current?.country?.trim().toUpperCase() === "IN" ? "inr" : "usd";
11792
11672
  const res = await api.buyCredits({
11793
11673
  serviceName: DEFAULT_TOPUP_SERVICE,
11794
- credits,
11674
+ credits: credits2,
11795
11675
  unitAmount: price,
11796
11676
  itemCount: 1,
11797
11677
  currency: topupCurrency,
@@ -11809,7 +11689,7 @@ function BillingContent({
11809
11689
  setAvailableCredits(payloadData.availableCredits);
11810
11690
  } else {
11811
11691
  setAvailableCredits(
11812
- (prev) => typeof prev === "number" ? prev + credits : prev
11692
+ (prev) => typeof prev === "number" ? prev + credits2 : prev
11813
11693
  );
11814
11694
  }
11815
11695
  toast.success("Top-up created");
@@ -11879,13 +11759,13 @@ function BillingContent({
11879
11759
  }
11880
11760
  ) });
11881
11761
  }
11882
- if (showCreditAllocations) {
11883
- 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: [
11884
- !onShowCreditAllocationsChange && /* @__PURE__ */ jsx28(BackToBilling, { onClick: () => setShowCreditAllocations(false) }),
11885
- /* @__PURE__ */ jsx28(CreditAllocationsView, {})
11762
+ if (showSharedCredits) {
11763
+ 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: [
11764
+ !onShowSharedCreditsChange && /* @__PURE__ */ jsx28(BackToBilling, { onClick: () => setShowSharedCredits(false) }),
11765
+ /* @__PURE__ */ jsx28(SharedCreditsView, {})
11886
11766
  ] });
11887
- if (embedded) return allocationsView;
11888
- return /* @__PURE__ */ jsx28("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: allocationsView });
11767
+ if (embedded) return sharedCreditsView;
11768
+ return /* @__PURE__ */ jsx28("div", { className: "flex-1 h-full min-h-0 min-w-0 relative", children: sharedCreditsView });
11889
11769
  }
11890
11770
  if (showPerUserLimits) {
11891
11771
  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: [
@@ -11952,7 +11832,7 @@ function BillingContent({
11952
11832
  {
11953
11833
  usageLabel: "Credits used",
11954
11834
  usageText,
11955
- remainingText: availableCredits != null ? `${formatCredits2(availableCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
11835
+ remainingText: remainingCredits != null ? `${formatCredits2(remainingCredits)} (${remainingPct}%) Remaining` : `${remainingPct}% Remaining`,
11956
11836
  buyButtonText: "Buy credits",
11957
11837
  buyButtonState: isFreePlan ? "disabled" : "default",
11958
11838
  buyButtonHoverText: isFreePlan ? "Pro plan required" : void 0,
@@ -11972,6 +11852,50 @@ function BillingContent({
11972
11852
  ]
11973
11853
  }
11974
11854
  ),
11855
+ sharedPool ? /* @__PURE__ */ jsxs25(
11856
+ "div",
11857
+ {
11858
+ role: "status",
11859
+ className: "flex flex-col shrink-0 w-full",
11860
+ style: {
11861
+ gap: 4,
11862
+ padding: 16,
11863
+ backgroundColor: "var(--alias-surface-base)",
11864
+ border: "1px solid var(--alias-border-subtle)",
11865
+ borderRadius: "var(--radius-3xl)"
11866
+ },
11867
+ children: [
11868
+ /* @__PURE__ */ jsx28(
11869
+ "span",
11870
+ {
11871
+ style: {
11872
+ fontFamily: "var(--font-family-font-family-body)",
11873
+ fontSize: "var(--font-size-font-size-sm)",
11874
+ fontWeight: 500,
11875
+ lineHeight: "var(--line-height-line-height-sm)",
11876
+ color: "var(--alias-text---icons-primary)"
11877
+ },
11878
+ children: sharedPoolBannerText(sharedPool)
11879
+ }
11880
+ ),
11881
+ /* @__PURE__ */ jsxs25(
11882
+ "span",
11883
+ {
11884
+ style: {
11885
+ fontFamily: "var(--font-family-font-family-body)",
11886
+ fontSize: "var(--font-size-font-size-sm)",
11887
+ lineHeight: "var(--line-height-line-height-sm)",
11888
+ color: "var(--alias-text---icons-muted)"
11889
+ },
11890
+ children: [
11891
+ "This workspace spends these shared credits, not its own; credits bought here can't be used while it does.",
11892
+ sharedPool.cycleEnd ? ` The allowance starts again on ${formatDate3(sharedPool.cycleEnd)}.` : ""
11893
+ ]
11894
+ }
11895
+ )
11896
+ ]
11897
+ }
11898
+ ) : null,
11975
11899
  usage ? /* @__PURE__ */ jsxs25(
11976
11900
  "div",
11977
11901
  {
@@ -12045,7 +11969,7 @@ function BillingContent({
12045
11969
  ]
12046
11970
  }
12047
11971
  ) : null,
12048
- canAllocateCredits ? /* @__PURE__ */ jsxs25(
11972
+ canShareCredits ? /* @__PURE__ */ jsxs25(
12049
11973
  "div",
12050
11974
  {
12051
11975
  className: "flex items-center justify-between shrink-0 w-full",
@@ -12068,7 +11992,7 @@ function BillingContent({
12068
11992
  lineHeight: "var(--line-height-line-height-sm)",
12069
11993
  color: "var(--alias-text---icons-primary)"
12070
11994
  },
12071
- children: "Credit allocations"
11995
+ children: "Shared credits"
12072
11996
  }
12073
11997
  ),
12074
11998
  /* @__PURE__ */ jsx28(
@@ -12080,7 +12004,7 @@ function BillingContent({
12080
12004
  lineHeight: "var(--line-height-line-height-sm)",
12081
12005
  color: "var(--alias-text---icons-muted)"
12082
12006
  },
12083
- children: "Share this workspace's credits with other workspaces in your organization"
12007
+ children: "Let other workspaces in your organization spend this workspace's credits"
12084
12008
  }
12085
12009
  )
12086
12010
  ] }),
@@ -12090,7 +12014,7 @@ function BillingContent({
12090
12014
  text: "Manage",
12091
12015
  variant: "secondary",
12092
12016
  size: "md",
12093
- onClick: () => setShowCreditAllocations(true)
12017
+ onClick: () => setShowSharedCredits(true)
12094
12018
  }
12095
12019
  )
12096
12020
  ]
@@ -12140,7 +12064,9 @@ function BillingContent({
12140
12064
  {
12141
12065
  logs: usageLogs,
12142
12066
  userMap,
12143
- loading: loadingTable
12067
+ loading: loadingTable,
12068
+ currentWorkspaceId: workspaceId,
12069
+ usedByNames
12144
12070
  }
12145
12071
  ),
12146
12072
  showExportModal && /* @__PURE__ */ jsx28(
@@ -12159,8 +12085,8 @@ function BillingContent({
12159
12085
  workspace.current?.country
12160
12086
  ),
12161
12087
  onClose: () => setShowBuyCreditsModal(false),
12162
- onBuy: (credits, price) => {
12163
- handleBuyCredits(credits, price);
12088
+ onBuy: (credits2, price) => {
12089
+ handleBuyCredits(credits2, price);
12164
12090
  }
12165
12091
  }
12166
12092
  )
@@ -13185,7 +13111,7 @@ function ConnectedAppsContent() {
13185
13111
 
13186
13112
  // src/organization-integrations-content.tsx
13187
13113
  import { useState as useState23 } from "react";
13188
- import { ScButton as ScButton16, ScToggleSwitch as ScToggleSwitch2 } from "@streamoid/ui";
13114
+ import { ScButton as ScButton16, ScToggleSwitch } from "@streamoid/ui";
13189
13115
  import { Fragment as Fragment14, jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
13190
13116
  var COLUMN = { app: "6.5rem", workspace: "9rem", date: "6.5rem", action: "4.75rem" };
13191
13117
  function HeaderRow() {
@@ -13314,7 +13240,7 @@ function AccessCard({
13314
13240
  /* @__PURE__ */ jsxs28("div", { className: "flex items-center shrink-0", style: { alignSelf: "center", gap: T15.spMd, minHeight: 26 }, children: [
13315
13241
  /* @__PURE__ */ jsx31("span", { style: { fontFamily: T15.font, fontSize: T15.fsSm, lineHeight: T15.lhSm, color: T15.muted }, children: enabled === null ? "" : enabled ? "On" : "Off" }),
13316
13242
  /* @__PURE__ */ jsx31("span", { style: { display: "inline-flex", width: 41, height: 25, alignItems: "center" }, children: /* @__PURE__ */ jsx31(
13317
- ScToggleSwitch2,
13243
+ ScToggleSwitch,
13318
13244
  {
13319
13245
  checked: !!enabled,
13320
13246
  disabled: enabled === null || loading,
@@ -14814,20 +14740,20 @@ function SettingsContentInner({
14814
14740
  const isBillingPlans = activeTab === "billing" && isWorkspaceAdmin && billingShowPlans;
14815
14741
  const [billingShowPerUserLimits, setBillingShowPerUserLimits] = useState24(false);
14816
14742
  const isBillingPerUserLimits = activeTab === "billing" && isWorkspaceAdmin && billingShowPerUserLimits && !billingShowPlans;
14817
- const [billingShowCreditAllocations, setBillingShowCreditAllocations] = useState24(false);
14818
- const isBillingCreditAllocations = activeTab === "billing" && isWorkspaceAdmin && billingShowCreditAllocations && !billingShowPlans && !billingShowPerUserLimits;
14819
- const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingCreditAllocations;
14743
+ const [billingShowSharedCredits, setBillingShowSharedCredits] = useState24(false);
14744
+ const isBillingSharedCredits = activeTab === "billing" && isWorkspaceAdmin && billingShowSharedCredits && !billingShowPlans && !billingShowPerUserLimits;
14745
+ const isBillingSubView = isBillingPlans || isBillingPerUserLimits || isBillingSharedCredits;
14820
14746
  useEffect19(() => {
14821
14747
  if (activeTab !== "billing" || !isWorkspaceAdmin) {
14822
14748
  setBillingShowPlans(false);
14823
14749
  setBillingShowPerUserLimits(false);
14824
- setBillingShowCreditAllocations(false);
14750
+ setBillingShowSharedCredits(false);
14825
14751
  }
14826
14752
  }, [activeTab, isWorkspaceAdmin]);
14827
14753
  useEffect19(() => {
14828
14754
  setBillingShowPlans(false);
14829
14755
  setBillingShowPerUserLimits(false);
14830
- setBillingShowCreditAllocations(false);
14756
+ setBillingShowSharedCredits(false);
14831
14757
  }, [workspaceId]);
14832
14758
  useEffect19(() => {
14833
14759
  if (!onClose) return;
@@ -14842,15 +14768,15 @@ function SettingsContentInner({
14842
14768
  setBillingShowPerUserLimits(false);
14843
14769
  return;
14844
14770
  }
14845
- if (billingShowCreditAllocations) {
14846
- setBillingShowCreditAllocations(false);
14771
+ if (billingShowSharedCredits) {
14772
+ setBillingShowSharedCredits(false);
14847
14773
  return;
14848
14774
  }
14849
14775
  onClose();
14850
14776
  };
14851
14777
  document.addEventListener("keydown", handleKeyDown);
14852
14778
  return () => document.removeEventListener("keydown", handleKeyDown);
14853
- }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowCreditAllocations]);
14779
+ }, [onClose, billingShowPlans, billingShowPerUserLimits, billingShowSharedCredits]);
14854
14780
  const [localWorkspaces, setLocalWorkspaces] = useState24(null);
14855
14781
  const [wsModalOpen, setWsModalOpen] = useState24(false);
14856
14782
  const [wsModalMode, setWsModalMode] = useState24("create");
@@ -15171,7 +15097,7 @@ function SettingsContentInner({
15171
15097
  if (isBillingSubView) {
15172
15098
  setBillingShowPlans(false);
15173
15099
  setBillingShowPerUserLimits(false);
15174
- setBillingShowCreditAllocations(false);
15100
+ setBillingShowSharedCredits(false);
15175
15101
  return;
15176
15102
  }
15177
15103
  if (onBack) onBack();
@@ -15274,7 +15200,7 @@ function SettingsContentInner({
15274
15200
  }
15275
15201
  )
15276
15202
  ] }),
15277
- isBillingCreditAllocations && /* @__PURE__ */ jsxs29(Fragment16, { children: [
15203
+ isBillingSharedCredits && /* @__PURE__ */ jsxs29(Fragment16, { children: [
15278
15204
  /* @__PURE__ */ jsx32(SiconRight3, { className: "w-6 h-6 shrink-0", color: "var(--alias-text---icons-primary)" }),
15279
15205
  /* @__PURE__ */ jsx32(
15280
15206
  "span",
@@ -15286,7 +15212,7 @@ function SettingsContentInner({
15286
15212
  lineHeight: "var(--line-height-line-height-xl)",
15287
15213
  color: "var(--alias-text---icons-primary)"
15288
15214
  },
15289
- children: "Credit allocations"
15215
+ children: "Shared credits"
15290
15216
  }
15291
15217
  )
15292
15218
  ] })
@@ -15499,7 +15425,7 @@ function SettingsContentInner({
15499
15425
  setBillingShowPlans(open);
15500
15426
  if (open) {
15501
15427
  setBillingShowPerUserLimits(false);
15502
- setBillingShowCreditAllocations(false);
15428
+ setBillingShowSharedCredits(false);
15503
15429
  }
15504
15430
  },
15505
15431
  showPerUserLimits: billingShowPerUserLimits,
@@ -15507,12 +15433,12 @@ function SettingsContentInner({
15507
15433
  setBillingShowPerUserLimits(open);
15508
15434
  if (open) {
15509
15435
  setBillingShowPlans(false);
15510
- setBillingShowCreditAllocations(false);
15436
+ setBillingShowSharedCredits(false);
15511
15437
  }
15512
15438
  },
15513
- showCreditAllocations: billingShowCreditAllocations,
15514
- onShowCreditAllocationsChange: (open) => {
15515
- setBillingShowCreditAllocations(open);
15439
+ showSharedCredits: billingShowSharedCredits,
15440
+ onShowSharedCreditsChange: (open) => {
15441
+ setBillingShowSharedCredits(open);
15516
15442
  if (open) {
15517
15443
  setBillingShowPlans(false);
15518
15444
  setBillingShowPerUserLimits(false);
@@ -16268,18 +16194,13 @@ function OrganizationPicker({
16268
16194
  );
16269
16195
  }
16270
16196
  export {
16271
- ALLOCATION_ERROR_CODES,
16272
- ALLOCATION_NOTE_MAX_LENGTH,
16273
16197
  APP_KEYS,
16274
16198
  BillingContent,
16275
16199
  BuyCreditsModal,
16276
- CANCEL_REASON_MAX_LENGTH,
16277
- CancelCreditAllocationModal,
16278
16200
  ConfirmDeleteModal,
16279
16201
  ConfirmLeaveModal,
16280
16202
  ConnectedAppsContent,
16281
- CreditAllocationModal,
16282
- CreditAllocationsView,
16203
+ CreditPoolMemberModal,
16283
16204
  DEFAULT_PERMISSIONS,
16284
16205
  DEFAULT_USER_LANGUAGE,
16285
16206
  ExportDateModal,
@@ -16292,15 +16213,20 @@ export {
16292
16213
  OrganizationMemberModal,
16293
16214
  OrganizationModal,
16294
16215
  OrganizationPicker,
16216
+ POOL_ERROR_CODES,
16217
+ POOL_NOTE_MAX_LENGTH,
16295
16218
  PerUserLimitsView,
16296
16219
  ProfileAvatar,
16297
16220
  ProfileTab,
16221
+ REMOVE_REASON_MAX_LENGTH,
16298
16222
  ReferralTab,
16223
+ RemovePoolMemberModal,
16299
16224
  SCOPE_LABELS,
16300
16225
  ScopeHeader,
16301
16226
  SettingsAdaptersProvider,
16302
16227
  SettingsContent,
16303
16228
  SettingsTooltip,
16229
+ SharedCreditsView,
16304
16230
  SiconKey_default as SiconKey,
16305
16231
  TAB_DESCRIPTIONS,
16306
16232
  TAB_LABELS,
@@ -16316,28 +16242,22 @@ export {
16316
16242
  WorkspaceModal,
16317
16243
  WorkspacePicker,
16318
16244
  WorkspaceTab,
16319
- allocationErrorMessage,
16320
- allocationWorkspaceName,
16321
16245
  belowUsageReason,
16322
- canShowCreditAllocations,
16323
- cancelOutcomeSummary,
16324
- cancelOutcomeWarning,
16325
- cleanAllocationValueInput,
16246
+ canShowSharedCredits,
16326
16247
  cleanCreditLimitInput,
16248
+ cleanPoolValueInput,
16327
16249
  countLabel,
16328
16250
  creditCycleUnit,
16329
- describeAllocation,
16251
+ creditsMeter,
16252
+ describeAllowance,
16330
16253
  describeDefaultLimit,
16331
16254
  describeMemberLimit,
16332
- findAllocationError,
16333
16255
  findBillingError,
16334
- formatAllocationRepeat,
16335
- formatAllocationSplit,
16256
+ findPoolError,
16336
16257
  formatCreditLimit,
16337
16258
  formatCredits,
16259
+ formatPoolSplit,
16338
16260
  groupWorkspaceList,
16339
- initialTransferSummary,
16340
- initialTransferWarning,
16341
16261
  isLimitBelowUsage,
16342
16262
  joinMembersWithLimits,
16343
16263
  lastUsedLabel,
@@ -16346,13 +16266,18 @@ export {
16346
16266
  loadWorkspaceOrganization,
16347
16267
  memberRoleUpdate,
16348
16268
  minimumCreditLimit,
16269
+ poolErrorMessage,
16270
+ poolWorkspaceName,
16349
16271
  resolveSettingsTabFromPath,
16350
16272
  roundCredits,
16273
+ sharedPoolBannerText,
16351
16274
  useSettingsAdapters,
16352
16275
  useWorkspaceOrganization,
16276
+ usedByNamesFrom,
16277
+ usedByWorkspaceKey,
16353
16278
  userLanguageLabel,
16354
- validateAllocationText,
16355
- validateAllocationValue,
16356
16279
  validateCreditLimitInput,
16357
- validateDefaultCreditLimitInput
16280
+ validateDefaultCreditLimitInput,
16281
+ validatePoolText,
16282
+ validatePoolValue
16358
16283
  };