@streamoid/settings 0.7.6 → 0.7.8

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/README.md CHANGED
@@ -223,3 +223,5 @@ and shares light/dark canvas, card, text, divider and selection colors with the
223
223
  host sidebar. Removing settings restores the host theme. Host wrappers must
224
224
  remain transparent and use `--alias-surface-canvas` for the app floor; do not
225
225
  paint `--alias-surface-base` behind the whole settings page.
226
+
227
+ Handled cancellations remain silent through the toast adapter. A confirmed member update or removal closes its editor before refreshing the list. The only-admin leave guard inspects retained diagnostics before selecting its authored guidance; diagnostic text is never rendered.
package/dist/index.d.mts CHANGED
@@ -366,9 +366,12 @@ interface OrganizationWorkspaceItem {
366
366
  imageUrl?: string;
367
367
  orgVisibility: "private" | "shared";
368
368
  /** Whether the viewer is in it. An org member is shown every shared
369
- * workspace; the ones with `joined: false` get a Join button (with
370
- * `joinWorkspaces`). Absent is treated as joined. */
369
+ * workspace, an org admin every workspace; the ones with `joined: false`
370
+ * get a Join button (with `joinWorkspaces`). Absent is treated as joined.
371
+ * A member joins as member, an org admin as admin. */
371
372
  joined?: boolean;
373
+ /** "inactive" workspaces are listed to org admins but can't be joined. */
374
+ status?: string;
372
375
  }
373
376
  /** A shared workspace in the organization that the viewer isn't in yet. */
374
377
  interface OrganizationJoinableWorkspace {
package/dist/index.d.ts CHANGED
@@ -366,9 +366,12 @@ interface OrganizationWorkspaceItem {
366
366
  imageUrl?: string;
367
367
  orgVisibility: "private" | "shared";
368
368
  /** Whether the viewer is in it. An org member is shown every shared
369
- * workspace; the ones with `joined: false` get a Join button (with
370
- * `joinWorkspaces`). Absent is treated as joined. */
369
+ * workspace, an org admin every workspace; the ones with `joined: false`
370
+ * get a Join button (with `joinWorkspaces`). Absent is treated as joined.
371
+ * A member joins as member, an org admin as admin. */
371
372
  joined?: boolean;
373
+ /** "inactive" workspaces are listed to org admins but can't be joined. */
374
+ status?: string;
372
375
  }
373
376
  /** A shared workspace in the organization that the viewer isn't in yet. */
374
377
  interface OrganizationJoinableWorkspace {
package/dist/index.js CHANGED
@@ -67,6 +67,161 @@ __export(index_exports, {
67
67
  });
68
68
  module.exports = __toCommonJS(index_exports);
69
69
 
70
+ // src/settings-errors.ts
71
+ function quietErrorToast(toast) {
72
+ return { ...toast, error(message) {
73
+ if (message.trim()) toast.error(message);
74
+ } };
75
+ }
76
+ function isOnlyWorkspaceAdmin(failure) {
77
+ const seen = /* @__PURE__ */ new Set();
78
+ const visit = (value, depth) => {
79
+ if (depth > 4 || value == null || seen.has(value)) return false;
80
+ if (typeof value === "string") return /single admin|only admin/i.test(value);
81
+ if (typeof value !== "object") return false;
82
+ seen.add(value);
83
+ const error = value;
84
+ return [error.body, error.data, error.status, error.message, error.error, error.cause].some((child) => visit(child, depth + 1));
85
+ };
86
+ return visit(failure, 0);
87
+ }
88
+
89
+ // ../errors/index.js
90
+ var actions = /* @__PURE__ */ new Set(["edit_input", "sign_in", "ask_admin", "wait", "view_status", "retry", "contact_support"]);
91
+ var states = /* @__PURE__ */ new Set(["not_started", "failed", "partial", "in_progress", "unknown"]);
92
+ var isObject = (value) => value !== null && typeof value === "object";
93
+ var object = (value) => isObject(value) ? value : {};
94
+ var text = (value) => typeof value === "string" ? value : "";
95
+ var reference = (value) => /^[a-zA-Z0-9][a-zA-Z0-9_-]{5,127}$/.test(text(value)) ? value : void 0;
96
+ var technical = /https?:\/\/|<[^>]+>|\b(?:TypeError|SyntaxError|ReferenceError|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|Traceback|SQLSTATE|MongoServerError|localhost|undefined|null|NaN|CORS|stack trace|Xvfb|node:sqlite)\b|(?:\/[^\s/]+){3,}|\bat\s+\w+\s*\(/i;
97
+ var publicText = (value) => {
98
+ const s = text(value).trim();
99
+ return s && s.length <= 500 && !technical.test(s) && !/[\r\n{}]/.test(s) ? s : void 0;
100
+ };
101
+ var isCancellation = (error) => object(error).name === "AbortError";
102
+ function parseRetryAfter(value, now = Date.now()) {
103
+ if (!text(value).trim()) return void 0;
104
+ const seconds = /^\d+$/.test(value.trim()) ? Number(value) : Math.ceil((Date.parse(value) - now) / 1e3);
105
+ return Number.isFinite(seconds) && seconds >= 0 && seconds <= 86400 ? seconds : void 0;
106
+ }
107
+ function validationFields(body) {
108
+ if (!Array.isArray(body.detail)) return [];
109
+ return body.detail.slice(0, 20).flatMap((issue) => {
110
+ if (!isObject(issue) || !Array.isArray(issue.loc)) return [];
111
+ const path = issue.loc.filter((part) => part !== "body" && part !== "query").filter((part) => typeof part === "string" || typeof part === "number").join(".");
112
+ if (!/^[a-zA-Z0-9_.-]{1,150}$/.test(path)) return [];
113
+ return [{ path, message: issue.type === "missing" || issue.type === "value_error.missing" ? "This field is required." : "Check this value and try again." }];
114
+ });
115
+ }
116
+ var known = {
117
+ "platform.session_expired": ["Your session has expired. Sign in to continue.", "sign_in"],
118
+ "platform.permission_denied": ["You don't have permission to do this. Ask a workspace admin for access.", "ask_admin"],
119
+ "platform.insufficient_credits": ["There aren't enough credits for this action. Ask your billing admin to add credits.", "ask_admin"],
120
+ "platform.invalid_input": ["Check the information you entered and correct any highlighted fields.", "edit_input"],
121
+ "platform.not_found": ["This item is no longer available or you don't have access to it. Check that you've opened the correct workspace.", "view_status"],
122
+ "platform.conflict": ["This item has changed or already exists. Check its current state before continuing.", "view_status"],
123
+ "platform.file_too_large": ["This file is too large. Choose a smaller file and try again.", "edit_input"],
124
+ "platform.unsupported_file": ["This file type isn't supported. Choose one of the accepted file types.", "edit_input"],
125
+ "platform.content_rejected": ["This request couldn't be completed with the current input. Review the image or description and try again.", "edit_input"],
126
+ "platform.rate_limited": ["This service is busy. Wait a moment before trying again.", "wait"],
127
+ "platform.service_unavailable": ["A service is temporarily unavailable. Try again shortly; contact support if it keeps happening.", "wait"],
128
+ "platform.setup_required": ["There's a setup problem on our side. Contact support so we can help.", "contact_support"],
129
+ "platform.network_error": ["We couldn't reach the service. Check your connection and try again shortly.", "retry"],
130
+ "platform.unexpected": ["We couldn't complete this action. Contact support if it keeps happening.", "contact_support"]
131
+ };
132
+ function inferCode(status, rawCode, diagnostic) {
133
+ if (Object.hasOwn(known, rawCode)) return rawCode;
134
+ const signal = `${rawCode} ${diagnostic}`;
135
+ if (/insufficient.?credits|credits.?unavailable|insufficient.?balance/i.test(signal)) return "platform.insufficient_credits";
136
+ if (/content.?policy|safety.?block|content.?reject/i.test(signal)) return "platform.content_rejected";
137
+ if (/rate.?limit|capacity.?limit/i.test(signal)) return "platform.rate_limited";
138
+ if (status === 401) return "platform.session_expired";
139
+ if (status === 402) return "platform.insufficient_credits";
140
+ if (status === 403) return "platform.permission_denied";
141
+ if (status === 404 || status === 410) return "platform.not_found";
142
+ if (status === 409 || status === 412) return "platform.conflict";
143
+ if (status === 413) return "platform.file_too_large";
144
+ if (status === 415) return "platform.unsupported_file";
145
+ if (status === 429) return "platform.rate_limited";
146
+ if (status === 400 || status === 422) return "platform.invalid_input";
147
+ if ([408, 502, 503, 504].includes(status)) return "platform.service_unavailable";
148
+ if (/ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|server misbehaving|upstream unavailable/i.test(signal)) return "platform.service_unavailable";
149
+ if (/failed to fetch|networkerror|network request failed|load failed/i.test(signal)) return "platform.network_error";
150
+ return "platform.unexpected";
151
+ }
152
+ function normalizeError(failure, options = {}) {
153
+ if (failure instanceof UserFacingError && Object.keys(options).length === 0) return failure.publicError;
154
+ const error = object(failure);
155
+ const body = object(options.body ?? error.body ?? error.data ?? failure);
156
+ const envelope = object(body.publicError ?? error.publicError);
157
+ const declared = envelope.version === 1 ? envelope : {};
158
+ const status = Number(options.status ?? error.statusCode ?? (typeof error.status === "number" ? error.status : void 0)) || 0;
159
+ const diagnostic = [text(error.message), text(failure), text(body.detail), text(body.error)].filter(Boolean).join(" ");
160
+ const rawCode = text(declared.code) || text(object(body.error).code) || text(body.code) || text(error.code);
161
+ const category = inferCode(status, rawCode, diagnostic);
162
+ const code = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/.test(text(declared.code)) ? declared.code : category;
163
+ const method = (options.method || text(error.method) || "UNKNOWN").toUpperCase();
164
+ const read = ["GET", "HEAD", "OPTIONS"].includes(method);
165
+ const suppliedState = object(declared.operation).state;
166
+ const state = states.has(suppliedState) ? suppliedState : read ? "failed" : "unknown";
167
+ const retryAfterSeconds = parseRetryAfter(options.retryAfter) ?? (Number.isFinite(declared.recovery?.retryAfterSeconds) && declared.recovery.retryAfterSeconds >= 0 && declared.recovery.retryAfterSeconds <= 86400 ? declared.recovery.retryAfterSeconds : void 0);
168
+ let [message, action] = known[category];
169
+ message = publicText(declared.message) || message;
170
+ if (actions.has(declared.recovery?.action)) action = declared.recovery.action;
171
+ const fieldErrors = Array.isArray(declared.fieldErrors) && declared.fieldErrors.length ? declared.fieldErrors.slice(0, 20).filter((item) => /^[a-zA-Z0-9_.-]{1,150}$/.test(text(item?.path)) && publicText(item?.message)).map((item) => ({ path: item.path, message: item.message })) : validationFields(body);
172
+ if (fieldErrors.length) {
173
+ const fields = fieldErrors.slice(0, 3).map((field) => `${field.path.replace(/_/g, " ")}: ${field.message}`).join(" ");
174
+ message = `${fields}${fieldErrors.length > 3 ? " Check the remaining fields too." : ""}`;
175
+ action = "edit_input";
176
+ }
177
+ if (category === "platform.rate_limited" && retryAfterSeconds !== void 0 && !publicText(declared.message)) {
178
+ const unit = retryAfterSeconds >= 3600 ? "hour" : retryAfterSeconds >= 60 ? "minute" : "second";
179
+ const amount = Math.ceil(retryAfterSeconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 1));
180
+ message = `This service is busy. Try again in ${amount} ${unit}${amount === 1 ? "" : "s"}.`;
181
+ }
182
+ const uncertain = !read && state === "unknown" && ["platform.network_error", "platform.service_unavailable", "platform.unexpected"].includes(category);
183
+ if (uncertain) {
184
+ const uncertainty = "We couldn't confirm whether your request completed. Check its status before trying again; contact support if you're unsure.";
185
+ const authored = publicText(declared.message);
186
+ message = authored ? authored.endsWith(uncertainty) ? authored : `${authored} ${uncertainty}` : uncertainty;
187
+ action = "view_status";
188
+ }
189
+ const operationLabel = publicText(options.operation);
190
+ if (operationLabel && !message.startsWith(`We couldn't ${operationLabel}.`)) message = `We couldn't ${operationLabel}. ${message}`;
191
+ const errorRef = reference(options.reference) || reference(declared.reference) || reference(error.errorRef) || reference(object(body.error).ref);
192
+ const safeToRetry = (read || declared.safeToRetry === true && ["not_started", "failed"].includes(state)) && ["wait", "retry"].includes(action);
193
+ return {
194
+ version: 1,
195
+ code,
196
+ message,
197
+ recovery: { action, ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {} },
198
+ operation: { state, ...reference(object(declared.operation).id) ? { id: declared.operation.id } : {} },
199
+ safeToRetry,
200
+ fieldErrors,
201
+ ...errorRef ? { reference: errorRef } : {}
202
+ };
203
+ }
204
+ var UserFacingError = class extends Error {
205
+ constructor(failure, options = {}) {
206
+ const normalized = normalizeError(failure, options);
207
+ super(normalized.message, { cause: failure });
208
+ this.name = "UserFacingError";
209
+ this.publicError = normalized;
210
+ this.status = Number(options.status ?? object(failure).status) || 0;
211
+ this.statusCode = this.status;
212
+ this.method = (options.method || text(object(failure).method) || "UNKNOWN").toUpperCase();
213
+ this.code = normalized.code;
214
+ this.body = options.body ?? object(failure).body;
215
+ this.data = this.body;
216
+ this.errorRef = normalized.reference ?? null;
217
+ }
218
+ };
219
+ function errorMessage(failure, options = {}) {
220
+ if (isCancellation(failure)) return "";
221
+ const normalized = normalizeError(failure, options);
222
+ return normalized.reference ? `${normalized.message} Reference: ${normalized.reference}` : normalized.message;
223
+ }
224
+
70
225
  // src/managed-organization-summary.tsx
71
226
  var import_icons = require("@streamoid/icons");
72
227
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -293,7 +448,13 @@ function SettingsAdaptersProvider({
293
448
  adapters,
294
449
  children
295
450
  }) {
296
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SettingsAdaptersContext.Provider, { value: adapters, children });
451
+ const value = (0, import_react2.useMemo)(() => {
452
+ const toast = quietErrorToast(adapters.toast);
453
+ return new Proxy(adapters, { get(target, key) {
454
+ return key === "toast" ? toast : Reflect.get(target, key);
455
+ } });
456
+ }, [adapters]);
457
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SettingsAdaptersContext.Provider, { value, children });
297
458
  }
298
459
  function useSettingsAdapters() {
299
460
  const ctx = (0, import_react2.useContext)(SettingsAdaptersContext);
@@ -2736,7 +2897,7 @@ function ToggleSwitch({
2736
2897
  }
2737
2898
  );
2738
2899
  }
2739
- function FieldLabel2({ text }) {
2900
+ function FieldLabel2({ text: text2 }) {
2740
2901
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex items-center shrink-0 w-full", style: { height: 24 }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2741
2902
  "div",
2742
2903
  {
@@ -2748,7 +2909,7 @@ function FieldLabel2({ text }) {
2748
2909
  lineHeight: T6.lhSm,
2749
2910
  color: T6.tertiary
2750
2911
  },
2751
- children: text
2912
+ children: text2
2752
2913
  }
2753
2914
  ) });
2754
2915
  }
@@ -4789,7 +4950,7 @@ function TeamsContent({
4789
4950
  toast.success("Invitation resent");
4790
4951
  await fetchData();
4791
4952
  } catch (err) {
4792
- const msg = err instanceof Error ? err.message : "Failed to resend invitation";
4953
+ const msg = errorMessage(err, { method: "POST" });
4793
4954
  toast.error(msg);
4794
4955
  }
4795
4956
  closeModal();
@@ -4824,17 +4985,13 @@ function TeamsContent({
4824
4985
  storeIds: permissions.catalogixStores || []
4825
4986
  });
4826
4987
  toast.success("User permissions updated");
4827
- await fetchData();
4828
4988
  } catch (err) {
4829
- const msg = err instanceof Error ? err.message : "Failed to update user";
4989
+ const msg = errorMessage(err, { method: "POST" });
4830
4990
  toast.error(msg);
4831
- const updateList = (list) => list.map(
4832
- (m) => m.id === modal.target?.id ? { ...m, permissions } : m
4833
- );
4834
- setActiveMembers(updateList);
4835
- setPendingMembers(updateList);
4991
+ return;
4836
4992
  }
4837
4993
  closeModal();
4994
+ await fetchData();
4838
4995
  };
4839
4996
  const handleRemove = async () => {
4840
4997
  if (!modal.target) {
@@ -4869,16 +5026,13 @@ function TeamsContent({
4869
5026
  } else {
4870
5027
  throw new Error("Unable to identify target to remove");
4871
5028
  }
4872
- await fetchData();
4873
5029
  } catch (err) {
4874
- const msg = err instanceof Error ? err.message : "Failed to remove user";
5030
+ const msg = errorMessage(err, { method: "POST" });
4875
5031
  toast.error(msg);
4876
- setActiveMembers((prev) => prev.filter((m) => m.id !== modal.target?.id));
4877
- setPendingMembers(
4878
- (prev) => prev.filter((m) => m.id !== modal.target?.id)
4879
- );
5032
+ return;
4880
5033
  }
4881
5034
  closeModal();
5035
+ await fetchData();
4882
5036
  };
4883
5037
  const handleCancelPendingInvite = async (member) => {
4884
5038
  if (member.kind !== "invite" || !member.invitationId) {
@@ -4904,7 +5058,7 @@ function TeamsContent({
4904
5058
  toast.success("Invitation removed");
4905
5059
  await fetchData();
4906
5060
  } catch (err) {
4907
- const msg = err instanceof Error ? err.message : "Failed to remove user";
5061
+ const msg = errorMessage(err, { method: "POST" });
4908
5062
  toast.error(msg);
4909
5063
  }
4910
5064
  };
@@ -4921,7 +5075,7 @@ function TeamsContent({
4921
5075
  toast.success("Invitation resent");
4922
5076
  await fetchData();
4923
5077
  } catch (err) {
4924
- const msg = err instanceof Error ? err.message : "Failed to resend invitation";
5078
+ const msg = errorMessage(err, { method: "POST" });
4925
5079
  toast.error(msg);
4926
5080
  }
4927
5081
  };
@@ -4950,7 +5104,7 @@ function TeamsContent({
4950
5104
  await fetchData();
4951
5105
  closeModal();
4952
5106
  } catch (err) {
4953
- const msg = err instanceof Error ? err.message : "Failed to send invite";
5107
+ const msg = errorMessage(err, { method: "POST" });
4954
5108
  toast.error(msg);
4955
5109
  }
4956
5110
  };
@@ -5166,7 +5320,7 @@ var T9 = {
5166
5320
  r4xl: "var(--radius-4xl)",
5167
5321
  neutral300: "var(--primitives-neutral-300)"
5168
5322
  };
5169
- function FieldLabel3({ text }) {
5323
+ function FieldLabel3({ text: text2 }) {
5170
5324
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex items-center shrink-0 w-full", style: { height: 24 }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5171
5325
  "div",
5172
5326
  {
@@ -5178,7 +5332,7 @@ function FieldLabel3({ text }) {
5178
5332
  lineHeight: T9.lhSm,
5179
5333
  color: T9.tertiary
5180
5334
  },
5181
- children: text
5335
+ children: text2
5182
5336
  }
5183
5337
  ) });
5184
5338
  }
@@ -6313,7 +6467,8 @@ function OrganizationContent({
6313
6467
  const current = organization.current;
6314
6468
  const [members, setMembers] = (0, import_react12.useState)([]);
6315
6469
  const [workspaces, setWorkspaces] = (0, import_react12.useState)([]);
6316
- const canJoinShared = current?.role === "member" && !!organization.joinWorkspaces;
6470
+ const joinRole = current?.role === "admin" || current?.role === "member" ? current.role : null;
6471
+ const canJoin = !!joinRole && !!organization.joinWorkspaces;
6317
6472
  const [joiningId, setJoiningId] = (0, import_react12.useState)(null);
6318
6473
  const [joinRequests, setJoinRequests] = (0, import_react12.useState)([]);
6319
6474
  const [highestRole, setHighestRole] = (0, import_react12.useState)("guest");
@@ -6355,9 +6510,7 @@ function OrganizationContent({
6355
6510
  await workspaceAdapter.refresh?.().catch(() => void 0);
6356
6511
  await fetchData();
6357
6512
  } catch (err) {
6358
- toast.error(
6359
- err instanceof Error ? err.message : `Couldn't join ${target.name}`
6360
- );
6513
+ toast.error(errorMessage(err, { method: "POST" }));
6361
6514
  } finally {
6362
6515
  setJoiningId(null);
6363
6516
  }
@@ -6370,7 +6523,7 @@ function OrganizationContent({
6370
6523
  toast.success("Organization updated");
6371
6524
  return true;
6372
6525
  } catch (err) {
6373
- toast.error(err instanceof Error ? err.message : "Failed to update organization");
6526
+ toast.error(errorMessage(err, { method: "POST" }));
6374
6527
  return false;
6375
6528
  }
6376
6529
  };
@@ -6381,7 +6534,7 @@ function OrganizationContent({
6381
6534
  toast.success("Organization deleted");
6382
6535
  return true;
6383
6536
  } catch (err) {
6384
- toast.error(err instanceof Error ? err.message : "Failed to delete organization");
6537
+ toast.error(errorMessage(err, { method: "POST" }));
6385
6538
  return false;
6386
6539
  }
6387
6540
  };
@@ -6413,7 +6566,7 @@ function OrganizationContent({
6413
6566
  toast.success("Member updated");
6414
6567
  await fetchData();
6415
6568
  } catch (err) {
6416
- toast.error(err instanceof Error ? err.message : "Failed to update member");
6569
+ toast.error(errorMessage(err, { method: "POST" }));
6417
6570
  }
6418
6571
  closeModal();
6419
6572
  };
@@ -6443,7 +6596,7 @@ function OrganizationContent({
6443
6596
  toast.success("Member removed");
6444
6597
  await fetchData();
6445
6598
  } catch (err) {
6446
- toast.error(err instanceof Error ? err.message : "Failed to remove member");
6599
+ toast.error(errorMessage(err, { method: "POST" }));
6447
6600
  }
6448
6601
  closeModal();
6449
6602
  };
@@ -6455,7 +6608,7 @@ function OrganizationContent({
6455
6608
  );
6456
6609
  await fetchData();
6457
6610
  } catch (err) {
6458
- toast.error(err instanceof Error ? err.message : "Failed to approve request");
6611
+ toast.error(errorMessage(err, { method: "POST" }));
6459
6612
  }
6460
6613
  };
6461
6614
  const handleRejectJoinRequest = async (request) => {
@@ -6464,7 +6617,7 @@ function OrganizationContent({
6464
6617
  toast.success("Request rejected");
6465
6618
  await fetchData();
6466
6619
  } catch (err) {
6467
- toast.error(err instanceof Error ? err.message : "Failed to reject request");
6620
+ toast.error(errorMessage(err, { method: "POST" }));
6468
6621
  }
6469
6622
  };
6470
6623
  if (!current) {
@@ -6592,7 +6745,7 @@ function OrganizationContent({
6592
6745
  {
6593
6746
  workspace: ws,
6594
6747
  joining: joiningId === ws._id,
6595
- onJoin: canJoinShared && ws.joined === false && ws.orgVisibility === "shared" ? () => void handleJoinWorkspace(ws) : void 0
6748
+ onJoin: canJoin && ws.joined === false && ws.status !== "inactive" && (joinRole === "admin" || ws.orgVisibility === "shared") ? () => void handleJoinWorkspace(ws) : void 0
6596
6749
  },
6597
6750
  ws._id
6598
6751
  ))
@@ -6694,6 +6847,21 @@ function OrganizationContent({
6694
6847
  ) });
6695
6848
  }
6696
6849
 
6850
+ // src/billing-errors.ts
6851
+ function isBillingWorkspaceMissing(failure) {
6852
+ const seen = /* @__PURE__ */ new Set();
6853
+ const visit = (value, depth) => {
6854
+ if (depth > 4 || value == null || seen.has(value)) return false;
6855
+ if (typeof value === "string") return /workspace.*not found/i.test(value);
6856
+ if (typeof value !== "object") return false;
6857
+ seen.add(value);
6858
+ const error = value;
6859
+ if (error.code === "billing.workspace_not_found") return true;
6860
+ return [error.message, error.error, error.data, error.body, error.cause, error.publicError].some((child) => visit(child, depth + 1));
6861
+ };
6862
+ return visit(failure, 0);
6863
+ }
6864
+
6697
6865
  // src/billing-content.tsx
6698
6866
  var import_react15 = require("react");
6699
6867
  var import_ui9 = require("@streamoid/ui");
@@ -8836,8 +9004,8 @@ function BillingContent({
8836
9004
  toast.success("Plan update submitted");
8837
9005
  await fetchBillingOverview();
8838
9006
  } catch (err) {
8839
- const message = err instanceof Error ? err.message : "Plan update failed";
8840
- const shouldCreateWorkspace = /workspace.*not found/i.test(message);
9007
+ const message = errorMessage(err, { method: "POST" });
9008
+ const shouldCreateWorkspace = isBillingWorkspaceMissing(err);
8841
9009
  if (shouldCreateWorkspace) {
8842
9010
  const created = await ensureWorkspaceOnBilling();
8843
9011
  if (created && api.updateSubscription) {
@@ -8890,7 +9058,7 @@ function BillingContent({
8890
9058
  toast.error("Unable to cancel downgrade");
8891
9059
  }
8892
9060
  } catch (err) {
8893
- const msg = err instanceof Error ? err.message : "Failed to cancel downgrade";
9061
+ const msg = errorMessage(err, { method: "POST" });
8894
9062
  toast.error(msg);
8895
9063
  } finally {
8896
9064
  setPlanActionInFlight(null);
@@ -9081,7 +9249,7 @@ function BillingContent({
9081
9249
  setShowBuyCreditsModal(false);
9082
9250
  await fetchBillingOverview();
9083
9251
  } catch (err) {
9084
- const message = err instanceof Error ? err.message : "Failed to create top-up session";
9252
+ const message = errorMessage(err, { method: "POST" });
9085
9253
  toast.error(message);
9086
9254
  } finally {
9087
9255
  setBuyCreditsInFlight(false);
@@ -9412,7 +9580,7 @@ function CreateTokenModal({ onClose, onCreated }) {
9412
9580
  const data = await api.createToken({ name: name.trim(), expiry });
9413
9581
  onCreated(data);
9414
9582
  } catch (err) {
9415
- toast.error(err instanceof Error ? err.message : "Failed to create token.");
9583
+ toast.error(errorMessage(err, { method: "POST" }));
9416
9584
  } finally {
9417
9585
  setLoading(false);
9418
9586
  }
@@ -9767,7 +9935,7 @@ function TokensContent({
9767
9935
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9768
9936
  }
9769
9937
  } catch (err) {
9770
- toast.error(err instanceof Error ? err.message : "Failed to revoke token.");
9938
+ toast.error(errorMessage(err, { method: "POST" }));
9771
9939
  } finally {
9772
9940
  setDeletingId(null);
9773
9941
  }
@@ -9779,7 +9947,7 @@ function TokensContent({
9779
9947
  toast.success("Token permanently deleted.");
9780
9948
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9781
9949
  } catch (err) {
9782
- toast.error(err instanceof Error ? err.message : "Failed to delete token.");
9950
+ toast.error(errorMessage(err, { method: "POST" }));
9783
9951
  } finally {
9784
9952
  setDeletingId(null);
9785
9953
  }
@@ -10617,7 +10785,7 @@ function ProfileTab() {
10617
10785
  await auth.refreshProfile();
10618
10786
  toast.success("Profile updated");
10619
10787
  } catch (err) {
10620
- const msg = err instanceof Error ? err.message : "Failed to update profile";
10788
+ const msg = errorMessage(err, { method: "POST" });
10621
10789
  toast.error(msg);
10622
10790
  } finally {
10623
10791
  setSaving(false);
@@ -10650,7 +10818,7 @@ function ProfileTab() {
10650
10818
  toast.success("Profile image updated");
10651
10819
  } catch (err) {
10652
10820
  setImagePreview(void 0);
10653
- const msg = err instanceof Error ? err.message : "Failed to update profile image";
10821
+ const msg = errorMessage(err, { method: "POST" });
10654
10822
  toast.error(msg);
10655
10823
  } finally {
10656
10824
  URL.revokeObjectURL(localPreviewUrl);
@@ -10670,7 +10838,7 @@ function ProfileTab() {
10670
10838
  await auth.refreshProfile();
10671
10839
  toast.success("Profile image removed");
10672
10840
  } catch (err) {
10673
- const msg = err instanceof Error ? err.message : "Failed to remove profile image";
10841
+ const msg = errorMessage(err, { method: "POST" });
10674
10842
  toast.error(msg);
10675
10843
  } finally {
10676
10844
  setImageSaving(false);
@@ -10961,9 +11129,9 @@ function groupWorkspaceList(workspaces, organizations = []) {
10961
11129
  });
10962
11130
  }
10963
11131
  }
10964
- const known = new Set(organizations.map((org) => org._id));
11132
+ const known2 = new Set(organizations.map((org) => org._id));
10965
11133
  const ungrouped = workspaces.filter(
10966
- (ws) => !ws.organizationId || !known.has(ws.organizationId)
11134
+ (ws) => !ws.organizationId || !known2.has(ws.organizationId)
10967
11135
  );
10968
11136
  if (ungrouped.length) {
10969
11137
  groups.push({
@@ -11514,17 +11682,6 @@ function SettingsContentInner({
11514
11682
  imageRemoved
11515
11683
  });
11516
11684
  };
11517
- const getErrorMessage = (err, fallback) => {
11518
- if (err instanceof Error && err.message.trim()) return err.message;
11519
- if (err && typeof err === "object" && "body" in err && err.body && typeof err.body === "object" && "status" in err.body && err.body.status && typeof err.body.status === "object" && "message" in err.body.status && typeof err.body.status.message === "string") {
11520
- return err.body.status.message;
11521
- }
11522
- return fallback;
11523
- };
11524
- const isSingleAdminError = (message) => {
11525
- const normalized = message.toLowerCase();
11526
- return normalized.includes("single admin") || normalized.includes("only admin");
11527
- };
11528
11685
  const getSettingsPath = (nextWorkspaceId) => `/dashboard/${nextWorkspaceId}/settings`;
11529
11686
  const getNextWorkspaceId = async (excludedWorkspaceId) => {
11530
11687
  try {
@@ -11608,7 +11765,7 @@ function SettingsContentInner({
11608
11765
  toast.success("Workspace updated");
11609
11766
  return true;
11610
11767
  } catch (err) {
11611
- toast.error(getErrorMessage(err, "Failed to update workspace"));
11768
+ toast.error(errorMessage(err, { method: "PATCH" }));
11612
11769
  return false;
11613
11770
  } finally {
11614
11771
  setUpdatingWorkspace(false);
@@ -11676,7 +11833,7 @@ function SettingsContentInner({
11676
11833
  toast.success("Workspace created");
11677
11834
  return true;
11678
11835
  } catch (err) {
11679
- const msg = err instanceof Error ? err.message : "Failed to create workspace";
11836
+ const msg = errorMessage(err, { method: "POST" });
11680
11837
  toast.error(msg);
11681
11838
  return false;
11682
11839
  } finally {
@@ -11723,7 +11880,7 @@ function SettingsContentInner({
11723
11880
  toast.success("Workspace updated");
11724
11881
  return true;
11725
11882
  } catch (err) {
11726
- const msg = err instanceof Error ? err.message : "Failed to update workspace";
11883
+ const msg = errorMessage(err, { method: "POST" });
11727
11884
  toast.error(msg);
11728
11885
  return false;
11729
11886
  } finally {
@@ -12199,11 +12356,10 @@ function SettingsContentInner({
12199
12356
  setLeavingWorkspace(null);
12200
12357
  await completeWorkspaceRemoval(wsId, `You left ${wsName}`);
12201
12358
  } catch (err) {
12202
- const message = getErrorMessage(err, "Failed to leave workspace");
12203
- if (isSingleAdminError(message)) {
12359
+ if (isOnlyWorkspaceAdmin(err)) {
12204
12360
  toast.error("You are the only admin. Promote another admin before leaving.");
12205
12361
  } else {
12206
- toast.error(message);
12362
+ toast.error(errorMessage(err, { method: "POST" }));
12207
12363
  }
12208
12364
  }
12209
12365
  },
@@ -12233,7 +12389,7 @@ function SettingsContentInner({
12233
12389
  setDeletingWorkspace(null);
12234
12390
  await completeWorkspaceRemoval(wsId, `Workspace ${wsName} deleted`);
12235
12391
  } catch (err) {
12236
- toast.error(getErrorMessage(err, "Failed to delete workspace"));
12392
+ toast.error(errorMessage(err, { method: "DELETE" }));
12237
12393
  }
12238
12394
  }
12239
12395
  }
package/dist/index.mjs CHANGED
@@ -1,3 +1,158 @@
1
+ // src/settings-errors.ts
2
+ function quietErrorToast(toast) {
3
+ return { ...toast, error(message) {
4
+ if (message.trim()) toast.error(message);
5
+ } };
6
+ }
7
+ function isOnlyWorkspaceAdmin(failure) {
8
+ const seen = /* @__PURE__ */ new Set();
9
+ const visit = (value, depth) => {
10
+ if (depth > 4 || value == null || seen.has(value)) return false;
11
+ if (typeof value === "string") return /single admin|only admin/i.test(value);
12
+ if (typeof value !== "object") return false;
13
+ seen.add(value);
14
+ const error = value;
15
+ return [error.body, error.data, error.status, error.message, error.error, error.cause].some((child) => visit(child, depth + 1));
16
+ };
17
+ return visit(failure, 0);
18
+ }
19
+
20
+ // ../errors/index.js
21
+ var actions = /* @__PURE__ */ new Set(["edit_input", "sign_in", "ask_admin", "wait", "view_status", "retry", "contact_support"]);
22
+ var states = /* @__PURE__ */ new Set(["not_started", "failed", "partial", "in_progress", "unknown"]);
23
+ var isObject = (value) => value !== null && typeof value === "object";
24
+ var object = (value) => isObject(value) ? value : {};
25
+ var text = (value) => typeof value === "string" ? value : "";
26
+ var reference = (value) => /^[a-zA-Z0-9][a-zA-Z0-9_-]{5,127}$/.test(text(value)) ? value : void 0;
27
+ var technical = /https?:\/\/|<[^>]+>|\b(?:TypeError|SyntaxError|ReferenceError|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|Traceback|SQLSTATE|MongoServerError|localhost|undefined|null|NaN|CORS|stack trace|Xvfb|node:sqlite)\b|(?:\/[^\s/]+){3,}|\bat\s+\w+\s*\(/i;
28
+ var publicText = (value) => {
29
+ const s = text(value).trim();
30
+ return s && s.length <= 500 && !technical.test(s) && !/[\r\n{}]/.test(s) ? s : void 0;
31
+ };
32
+ var isCancellation = (error) => object(error).name === "AbortError";
33
+ function parseRetryAfter(value, now = Date.now()) {
34
+ if (!text(value).trim()) return void 0;
35
+ const seconds = /^\d+$/.test(value.trim()) ? Number(value) : Math.ceil((Date.parse(value) - now) / 1e3);
36
+ return Number.isFinite(seconds) && seconds >= 0 && seconds <= 86400 ? seconds : void 0;
37
+ }
38
+ function validationFields(body) {
39
+ if (!Array.isArray(body.detail)) return [];
40
+ return body.detail.slice(0, 20).flatMap((issue) => {
41
+ if (!isObject(issue) || !Array.isArray(issue.loc)) return [];
42
+ const path = issue.loc.filter((part) => part !== "body" && part !== "query").filter((part) => typeof part === "string" || typeof part === "number").join(".");
43
+ if (!/^[a-zA-Z0-9_.-]{1,150}$/.test(path)) return [];
44
+ return [{ path, message: issue.type === "missing" || issue.type === "value_error.missing" ? "This field is required." : "Check this value and try again." }];
45
+ });
46
+ }
47
+ var known = {
48
+ "platform.session_expired": ["Your session has expired. Sign in to continue.", "sign_in"],
49
+ "platform.permission_denied": ["You don't have permission to do this. Ask a workspace admin for access.", "ask_admin"],
50
+ "platform.insufficient_credits": ["There aren't enough credits for this action. Ask your billing admin to add credits.", "ask_admin"],
51
+ "platform.invalid_input": ["Check the information you entered and correct any highlighted fields.", "edit_input"],
52
+ "platform.not_found": ["This item is no longer available or you don't have access to it. Check that you've opened the correct workspace.", "view_status"],
53
+ "platform.conflict": ["This item has changed or already exists. Check its current state before continuing.", "view_status"],
54
+ "platform.file_too_large": ["This file is too large. Choose a smaller file and try again.", "edit_input"],
55
+ "platform.unsupported_file": ["This file type isn't supported. Choose one of the accepted file types.", "edit_input"],
56
+ "platform.content_rejected": ["This request couldn't be completed with the current input. Review the image or description and try again.", "edit_input"],
57
+ "platform.rate_limited": ["This service is busy. Wait a moment before trying again.", "wait"],
58
+ "platform.service_unavailable": ["A service is temporarily unavailable. Try again shortly; contact support if it keeps happening.", "wait"],
59
+ "platform.setup_required": ["There's a setup problem on our side. Contact support so we can help.", "contact_support"],
60
+ "platform.network_error": ["We couldn't reach the service. Check your connection and try again shortly.", "retry"],
61
+ "platform.unexpected": ["We couldn't complete this action. Contact support if it keeps happening.", "contact_support"]
62
+ };
63
+ function inferCode(status, rawCode, diagnostic) {
64
+ if (Object.hasOwn(known, rawCode)) return rawCode;
65
+ const signal = `${rawCode} ${diagnostic}`;
66
+ if (/insufficient.?credits|credits.?unavailable|insufficient.?balance/i.test(signal)) return "platform.insufficient_credits";
67
+ if (/content.?policy|safety.?block|content.?reject/i.test(signal)) return "platform.content_rejected";
68
+ if (/rate.?limit|capacity.?limit/i.test(signal)) return "platform.rate_limited";
69
+ if (status === 401) return "platform.session_expired";
70
+ if (status === 402) return "platform.insufficient_credits";
71
+ if (status === 403) return "platform.permission_denied";
72
+ if (status === 404 || status === 410) return "platform.not_found";
73
+ if (status === 409 || status === 412) return "platform.conflict";
74
+ if (status === 413) return "platform.file_too_large";
75
+ if (status === 415) return "platform.unsupported_file";
76
+ if (status === 429) return "platform.rate_limited";
77
+ if (status === 400 || status === 422) return "platform.invalid_input";
78
+ if ([408, 502, 503, 504].includes(status)) return "platform.service_unavailable";
79
+ if (/ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|server misbehaving|upstream unavailable/i.test(signal)) return "platform.service_unavailable";
80
+ if (/failed to fetch|networkerror|network request failed|load failed/i.test(signal)) return "platform.network_error";
81
+ return "platform.unexpected";
82
+ }
83
+ function normalizeError(failure, options = {}) {
84
+ if (failure instanceof UserFacingError && Object.keys(options).length === 0) return failure.publicError;
85
+ const error = object(failure);
86
+ const body = object(options.body ?? error.body ?? error.data ?? failure);
87
+ const envelope = object(body.publicError ?? error.publicError);
88
+ const declared = envelope.version === 1 ? envelope : {};
89
+ const status = Number(options.status ?? error.statusCode ?? (typeof error.status === "number" ? error.status : void 0)) || 0;
90
+ const diagnostic = [text(error.message), text(failure), text(body.detail), text(body.error)].filter(Boolean).join(" ");
91
+ const rawCode = text(declared.code) || text(object(body.error).code) || text(body.code) || text(error.code);
92
+ const category = inferCode(status, rawCode, diagnostic);
93
+ const code = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/.test(text(declared.code)) ? declared.code : category;
94
+ const method = (options.method || text(error.method) || "UNKNOWN").toUpperCase();
95
+ const read = ["GET", "HEAD", "OPTIONS"].includes(method);
96
+ const suppliedState = object(declared.operation).state;
97
+ const state = states.has(suppliedState) ? suppliedState : read ? "failed" : "unknown";
98
+ const retryAfterSeconds = parseRetryAfter(options.retryAfter) ?? (Number.isFinite(declared.recovery?.retryAfterSeconds) && declared.recovery.retryAfterSeconds >= 0 && declared.recovery.retryAfterSeconds <= 86400 ? declared.recovery.retryAfterSeconds : void 0);
99
+ let [message, action] = known[category];
100
+ message = publicText(declared.message) || message;
101
+ if (actions.has(declared.recovery?.action)) action = declared.recovery.action;
102
+ const fieldErrors = Array.isArray(declared.fieldErrors) && declared.fieldErrors.length ? declared.fieldErrors.slice(0, 20).filter((item) => /^[a-zA-Z0-9_.-]{1,150}$/.test(text(item?.path)) && publicText(item?.message)).map((item) => ({ path: item.path, message: item.message })) : validationFields(body);
103
+ if (fieldErrors.length) {
104
+ const fields = fieldErrors.slice(0, 3).map((field) => `${field.path.replace(/_/g, " ")}: ${field.message}`).join(" ");
105
+ message = `${fields}${fieldErrors.length > 3 ? " Check the remaining fields too." : ""}`;
106
+ action = "edit_input";
107
+ }
108
+ if (category === "platform.rate_limited" && retryAfterSeconds !== void 0 && !publicText(declared.message)) {
109
+ const unit = retryAfterSeconds >= 3600 ? "hour" : retryAfterSeconds >= 60 ? "minute" : "second";
110
+ const amount = Math.ceil(retryAfterSeconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 1));
111
+ message = `This service is busy. Try again in ${amount} ${unit}${amount === 1 ? "" : "s"}.`;
112
+ }
113
+ const uncertain = !read && state === "unknown" && ["platform.network_error", "platform.service_unavailable", "platform.unexpected"].includes(category);
114
+ if (uncertain) {
115
+ const uncertainty = "We couldn't confirm whether your request completed. Check its status before trying again; contact support if you're unsure.";
116
+ const authored = publicText(declared.message);
117
+ message = authored ? authored.endsWith(uncertainty) ? authored : `${authored} ${uncertainty}` : uncertainty;
118
+ action = "view_status";
119
+ }
120
+ const operationLabel = publicText(options.operation);
121
+ if (operationLabel && !message.startsWith(`We couldn't ${operationLabel}.`)) message = `We couldn't ${operationLabel}. ${message}`;
122
+ const errorRef = reference(options.reference) || reference(declared.reference) || reference(error.errorRef) || reference(object(body.error).ref);
123
+ const safeToRetry = (read || declared.safeToRetry === true && ["not_started", "failed"].includes(state)) && ["wait", "retry"].includes(action);
124
+ return {
125
+ version: 1,
126
+ code,
127
+ message,
128
+ recovery: { action, ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {} },
129
+ operation: { state, ...reference(object(declared.operation).id) ? { id: declared.operation.id } : {} },
130
+ safeToRetry,
131
+ fieldErrors,
132
+ ...errorRef ? { reference: errorRef } : {}
133
+ };
134
+ }
135
+ var UserFacingError = class extends Error {
136
+ constructor(failure, options = {}) {
137
+ const normalized = normalizeError(failure, options);
138
+ super(normalized.message, { cause: failure });
139
+ this.name = "UserFacingError";
140
+ this.publicError = normalized;
141
+ this.status = Number(options.status ?? object(failure).status) || 0;
142
+ this.statusCode = this.status;
143
+ this.method = (options.method || text(object(failure).method) || "UNKNOWN").toUpperCase();
144
+ this.code = normalized.code;
145
+ this.body = options.body ?? object(failure).body;
146
+ this.data = this.body;
147
+ this.errorRef = normalized.reference ?? null;
148
+ }
149
+ };
150
+ function errorMessage(failure, options = {}) {
151
+ if (isCancellation(failure)) return "";
152
+ const normalized = normalizeError(failure, options);
153
+ return normalized.reference ? `${normalized.message} Reference: ${normalized.reference}` : normalized.message;
154
+ }
155
+
1
156
  // src/managed-organization-summary.tsx
2
157
  import { SiconExternalLink } from "@streamoid/icons";
3
158
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -168,7 +323,7 @@ import {
168
323
  useEffect as useEffect13,
169
324
  useRef as useRef7,
170
325
  useCallback as useCallback7,
171
- useMemo as useMemo3
326
+ useMemo as useMemo4
172
327
  } from "react";
173
328
  import {
174
329
  ScSidebarMenu,
@@ -249,14 +404,20 @@ function userLanguageLabel(code) {
249
404
  import { useState as useState2, useRef as useRef2, useEffect as useEffect3 } from "react";
250
405
 
251
406
  // src/settings-adapters-context.tsx
252
- import { createContext, useContext } from "react";
407
+ import { createContext, useContext, useMemo } from "react";
253
408
  import { jsx as jsx4 } from "react/jsx-runtime";
254
409
  var SettingsAdaptersContext = createContext(null);
255
410
  function SettingsAdaptersProvider({
256
411
  adapters,
257
412
  children
258
413
  }) {
259
- return /* @__PURE__ */ jsx4(SettingsAdaptersContext.Provider, { value: adapters, children });
414
+ const value = useMemo(() => {
415
+ const toast = quietErrorToast(adapters.toast);
416
+ return new Proxy(adapters, { get(target, key) {
417
+ return key === "toast" ? toast : Reflect.get(target, key);
418
+ } });
419
+ }, [adapters]);
420
+ return /* @__PURE__ */ jsx4(SettingsAdaptersContext.Provider, { value, children });
260
421
  }
261
422
  function useSettingsAdapters() {
262
423
  const ctx = useContext(SettingsAdaptersContext);
@@ -2713,7 +2874,7 @@ function ToggleSwitch({
2713
2874
  }
2714
2875
  );
2715
2876
  }
2716
- function FieldLabel2({ text }) {
2877
+ function FieldLabel2({ text: text2 }) {
2717
2878
  return /* @__PURE__ */ jsx12("div", { className: "flex items-center shrink-0 w-full", style: { height: 24 }, children: /* @__PURE__ */ jsx12(
2718
2879
  "div",
2719
2880
  {
@@ -2725,7 +2886,7 @@ function FieldLabel2({ text }) {
2725
2886
  lineHeight: T6.lhSm,
2726
2887
  color: T6.tertiary
2727
2888
  },
2728
- children: text
2889
+ children: text2
2729
2890
  }
2730
2891
  ) });
2731
2892
  }
@@ -4766,7 +4927,7 @@ function TeamsContent({
4766
4927
  toast.success("Invitation resent");
4767
4928
  await fetchData();
4768
4929
  } catch (err) {
4769
- const msg = err instanceof Error ? err.message : "Failed to resend invitation";
4930
+ const msg = errorMessage(err, { method: "POST" });
4770
4931
  toast.error(msg);
4771
4932
  }
4772
4933
  closeModal();
@@ -4801,17 +4962,13 @@ function TeamsContent({
4801
4962
  storeIds: permissions.catalogixStores || []
4802
4963
  });
4803
4964
  toast.success("User permissions updated");
4804
- await fetchData();
4805
4965
  } catch (err) {
4806
- const msg = err instanceof Error ? err.message : "Failed to update user";
4966
+ const msg = errorMessage(err, { method: "POST" });
4807
4967
  toast.error(msg);
4808
- const updateList = (list) => list.map(
4809
- (m) => m.id === modal.target?.id ? { ...m, permissions } : m
4810
- );
4811
- setActiveMembers(updateList);
4812
- setPendingMembers(updateList);
4968
+ return;
4813
4969
  }
4814
4970
  closeModal();
4971
+ await fetchData();
4815
4972
  };
4816
4973
  const handleRemove = async () => {
4817
4974
  if (!modal.target) {
@@ -4846,16 +5003,13 @@ function TeamsContent({
4846
5003
  } else {
4847
5004
  throw new Error("Unable to identify target to remove");
4848
5005
  }
4849
- await fetchData();
4850
5006
  } catch (err) {
4851
- const msg = err instanceof Error ? err.message : "Failed to remove user";
5007
+ const msg = errorMessage(err, { method: "POST" });
4852
5008
  toast.error(msg);
4853
- setActiveMembers((prev) => prev.filter((m) => m.id !== modal.target?.id));
4854
- setPendingMembers(
4855
- (prev) => prev.filter((m) => m.id !== modal.target?.id)
4856
- );
5009
+ return;
4857
5010
  }
4858
5011
  closeModal();
5012
+ await fetchData();
4859
5013
  };
4860
5014
  const handleCancelPendingInvite = async (member) => {
4861
5015
  if (member.kind !== "invite" || !member.invitationId) {
@@ -4881,7 +5035,7 @@ function TeamsContent({
4881
5035
  toast.success("Invitation removed");
4882
5036
  await fetchData();
4883
5037
  } catch (err) {
4884
- const msg = err instanceof Error ? err.message : "Failed to remove user";
5038
+ const msg = errorMessage(err, { method: "POST" });
4885
5039
  toast.error(msg);
4886
5040
  }
4887
5041
  };
@@ -4898,7 +5052,7 @@ function TeamsContent({
4898
5052
  toast.success("Invitation resent");
4899
5053
  await fetchData();
4900
5054
  } catch (err) {
4901
- const msg = err instanceof Error ? err.message : "Failed to resend invitation";
5055
+ const msg = errorMessage(err, { method: "POST" });
4902
5056
  toast.error(msg);
4903
5057
  }
4904
5058
  };
@@ -4927,7 +5081,7 @@ function TeamsContent({
4927
5081
  await fetchData();
4928
5082
  closeModal();
4929
5083
  } catch (err) {
4930
- const msg = err instanceof Error ? err.message : "Failed to send invite";
5084
+ const msg = errorMessage(err, { method: "POST" });
4931
5085
  toast.error(msg);
4932
5086
  }
4933
5087
  };
@@ -5143,7 +5297,7 @@ var T9 = {
5143
5297
  r4xl: "var(--radius-4xl)",
5144
5298
  neutral300: "var(--primitives-neutral-300)"
5145
5299
  };
5146
- function FieldLabel3({ text }) {
5300
+ function FieldLabel3({ text: text2 }) {
5147
5301
  return /* @__PURE__ */ jsx15("div", { className: "flex items-center shrink-0 w-full", style: { height: 24 }, children: /* @__PURE__ */ jsx15(
5148
5302
  "div",
5149
5303
  {
@@ -5155,7 +5309,7 @@ function FieldLabel3({ text }) {
5155
5309
  lineHeight: T9.lhSm,
5156
5310
  color: T9.tertiary
5157
5311
  },
5158
- children: text
5312
+ children: text2
5159
5313
  }
5160
5314
  ) });
5161
5315
  }
@@ -6290,7 +6444,8 @@ function OrganizationContent({
6290
6444
  const current = organization.current;
6291
6445
  const [members, setMembers] = useState10([]);
6292
6446
  const [workspaces, setWorkspaces] = useState10([]);
6293
- const canJoinShared = current?.role === "member" && !!organization.joinWorkspaces;
6447
+ const joinRole = current?.role === "admin" || current?.role === "member" ? current.role : null;
6448
+ const canJoin = !!joinRole && !!organization.joinWorkspaces;
6294
6449
  const [joiningId, setJoiningId] = useState10(null);
6295
6450
  const [joinRequests, setJoinRequests] = useState10([]);
6296
6451
  const [highestRole, setHighestRole] = useState10("guest");
@@ -6332,9 +6487,7 @@ function OrganizationContent({
6332
6487
  await workspaceAdapter.refresh?.().catch(() => void 0);
6333
6488
  await fetchData();
6334
6489
  } catch (err) {
6335
- toast.error(
6336
- err instanceof Error ? err.message : `Couldn't join ${target.name}`
6337
- );
6490
+ toast.error(errorMessage(err, { method: "POST" }));
6338
6491
  } finally {
6339
6492
  setJoiningId(null);
6340
6493
  }
@@ -6347,7 +6500,7 @@ function OrganizationContent({
6347
6500
  toast.success("Organization updated");
6348
6501
  return true;
6349
6502
  } catch (err) {
6350
- toast.error(err instanceof Error ? err.message : "Failed to update organization");
6503
+ toast.error(errorMessage(err, { method: "POST" }));
6351
6504
  return false;
6352
6505
  }
6353
6506
  };
@@ -6358,7 +6511,7 @@ function OrganizationContent({
6358
6511
  toast.success("Organization deleted");
6359
6512
  return true;
6360
6513
  } catch (err) {
6361
- toast.error(err instanceof Error ? err.message : "Failed to delete organization");
6514
+ toast.error(errorMessage(err, { method: "POST" }));
6362
6515
  return false;
6363
6516
  }
6364
6517
  };
@@ -6390,7 +6543,7 @@ function OrganizationContent({
6390
6543
  toast.success("Member updated");
6391
6544
  await fetchData();
6392
6545
  } catch (err) {
6393
- toast.error(err instanceof Error ? err.message : "Failed to update member");
6546
+ toast.error(errorMessage(err, { method: "POST" }));
6394
6547
  }
6395
6548
  closeModal();
6396
6549
  };
@@ -6420,7 +6573,7 @@ function OrganizationContent({
6420
6573
  toast.success("Member removed");
6421
6574
  await fetchData();
6422
6575
  } catch (err) {
6423
- toast.error(err instanceof Error ? err.message : "Failed to remove member");
6576
+ toast.error(errorMessage(err, { method: "POST" }));
6424
6577
  }
6425
6578
  closeModal();
6426
6579
  };
@@ -6432,7 +6585,7 @@ function OrganizationContent({
6432
6585
  );
6433
6586
  await fetchData();
6434
6587
  } catch (err) {
6435
- toast.error(err instanceof Error ? err.message : "Failed to approve request");
6588
+ toast.error(errorMessage(err, { method: "POST" }));
6436
6589
  }
6437
6590
  };
6438
6591
  const handleRejectJoinRequest = async (request) => {
@@ -6441,7 +6594,7 @@ function OrganizationContent({
6441
6594
  toast.success("Request rejected");
6442
6595
  await fetchData();
6443
6596
  } catch (err) {
6444
- toast.error(err instanceof Error ? err.message : "Failed to reject request");
6597
+ toast.error(errorMessage(err, { method: "POST" }));
6445
6598
  }
6446
6599
  };
6447
6600
  if (!current) {
@@ -6569,7 +6722,7 @@ function OrganizationContent({
6569
6722
  {
6570
6723
  workspace: ws,
6571
6724
  joining: joiningId === ws._id,
6572
- onJoin: canJoinShared && ws.joined === false && ws.orgVisibility === "shared" ? () => void handleJoinWorkspace(ws) : void 0
6725
+ onJoin: canJoin && ws.joined === false && ws.status !== "inactive" && (joinRole === "admin" || ws.orgVisibility === "shared") ? () => void handleJoinWorkspace(ws) : void 0
6573
6726
  },
6574
6727
  ws._id
6575
6728
  ))
@@ -6671,12 +6824,27 @@ function OrganizationContent({
6671
6824
  ) });
6672
6825
  }
6673
6826
 
6827
+ // src/billing-errors.ts
6828
+ function isBillingWorkspaceMissing(failure) {
6829
+ const seen = /* @__PURE__ */ new Set();
6830
+ const visit = (value, depth) => {
6831
+ if (depth > 4 || value == null || seen.has(value)) return false;
6832
+ if (typeof value === "string") return /workspace.*not found/i.test(value);
6833
+ if (typeof value !== "object") return false;
6834
+ seen.add(value);
6835
+ const error = value;
6836
+ if (error.code === "billing.workspace_not_found") return true;
6837
+ return [error.message, error.error, error.data, error.body, error.cause, error.publicError].some((child) => visit(child, depth + 1));
6838
+ };
6839
+ return visit(failure, 0);
6840
+ }
6841
+
6674
6842
  // src/billing-content.tsx
6675
6843
  import {
6676
6844
  useState as useState13,
6677
6845
  useEffect as useEffect11,
6678
6846
  useCallback as useCallback5,
6679
- useMemo as useMemo2,
6847
+ useMemo as useMemo3,
6680
6848
  useRef as useRef6
6681
6849
  } from "react";
6682
6850
  import {
@@ -6782,7 +6950,7 @@ var PLAN_TAGLINES = {
6782
6950
  };
6783
6951
 
6784
6952
  // src/export-date-modal.tsx
6785
- import { useState as useState11, useMemo, useCallback as useCallback4 } from "react";
6953
+ import { useState as useState11, useMemo as useMemo2, useCallback as useCallback4 } from "react";
6786
6954
  import { SiconClose as SiconClose4, SiconLeft, SiconRight } from "@streamoid/icons";
6787
6955
  import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
6788
6956
  var PRIMARY_COLOR = "var(--alias-text---icons-primary)";
@@ -6837,7 +7005,7 @@ function ExportDateModal({
6837
7005
  const [rangeEnd, setRangeEnd] = useState11(null);
6838
7006
  const isFuture = (date) => date.getTime() > today.getTime();
6839
7007
  const isCurrentMonthView = viewMonth === now.getMonth() && viewYear === now.getFullYear();
6840
- const cells = useMemo(() => {
7008
+ const cells = useMemo2(() => {
6841
7009
  const result = [];
6842
7010
  const daysInMonth = getDaysInMonth(viewYear, viewMonth);
6843
7011
  const firstDay = new Date(viewYear, viewMonth, 1).getDay();
@@ -8000,7 +8168,7 @@ function PlansView({
8000
8168
  const workspaceCurrencySymbol = getCurrencySymbolByCountry(
8001
8169
  workspace?.country
8002
8170
  );
8003
- const allPlans = useMemo2(() => {
8171
+ const allPlans = useMemo3(() => {
8004
8172
  if (products.length > 0) {
8005
8173
  return products.flatMap(
8006
8174
  (product) => product.prices.filter((price) => Boolean(price.metadata?.credits)).map((price) => ({
@@ -8018,7 +8186,7 @@ function PlansView({
8018
8186
  }
8019
8187
  return [];
8020
8188
  }, [products, currentPlanId]);
8021
- const sortedPlans = useMemo2(
8189
+ const sortedPlans = useMemo3(
8022
8190
  () => [...allPlans].sort((a, b) => a.amount - b.amount),
8023
8191
  [allPlans]
8024
8192
  );
@@ -8834,8 +9002,8 @@ function BillingContent({
8834
9002
  toast.success("Plan update submitted");
8835
9003
  await fetchBillingOverview();
8836
9004
  } catch (err) {
8837
- const message = err instanceof Error ? err.message : "Plan update failed";
8838
- const shouldCreateWorkspace = /workspace.*not found/i.test(message);
9005
+ const message = errorMessage(err, { method: "POST" });
9006
+ const shouldCreateWorkspace = isBillingWorkspaceMissing(err);
8839
9007
  if (shouldCreateWorkspace) {
8840
9008
  const created = await ensureWorkspaceOnBilling();
8841
9009
  if (created && api.updateSubscription) {
@@ -8888,7 +9056,7 @@ function BillingContent({
8888
9056
  toast.error("Unable to cancel downgrade");
8889
9057
  }
8890
9058
  } catch (err) {
8891
- const msg = err instanceof Error ? err.message : "Failed to cancel downgrade";
9059
+ const msg = errorMessage(err, { method: "POST" });
8892
9060
  toast.error(msg);
8893
9061
  } finally {
8894
9062
  setPlanActionInFlight(null);
@@ -9079,7 +9247,7 @@ function BillingContent({
9079
9247
  setShowBuyCreditsModal(false);
9080
9248
  await fetchBillingOverview();
9081
9249
  } catch (err) {
9082
- const message = err instanceof Error ? err.message : "Failed to create top-up session";
9250
+ const message = errorMessage(err, { method: "POST" });
9083
9251
  toast.error(message);
9084
9252
  } finally {
9085
9253
  setBuyCreditsInFlight(false);
@@ -9416,7 +9584,7 @@ function CreateTokenModal({ onClose, onCreated }) {
9416
9584
  const data = await api.createToken({ name: name.trim(), expiry });
9417
9585
  onCreated(data);
9418
9586
  } catch (err) {
9419
- toast.error(err instanceof Error ? err.message : "Failed to create token.");
9587
+ toast.error(errorMessage(err, { method: "POST" }));
9420
9588
  } finally {
9421
9589
  setLoading(false);
9422
9590
  }
@@ -9771,7 +9939,7 @@ function TokensContent({
9771
9939
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9772
9940
  }
9773
9941
  } catch (err) {
9774
- toast.error(err instanceof Error ? err.message : "Failed to revoke token.");
9942
+ toast.error(errorMessage(err, { method: "POST" }));
9775
9943
  } finally {
9776
9944
  setDeletingId(null);
9777
9945
  }
@@ -9783,7 +9951,7 @@ function TokensContent({
9783
9951
  toast.success("Token permanently deleted.");
9784
9952
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9785
9953
  } catch (err) {
9786
- toast.error(err instanceof Error ? err.message : "Failed to delete token.");
9954
+ toast.error(errorMessage(err, { method: "POST" }));
9787
9955
  } finally {
9788
9956
  setDeletingId(null);
9789
9957
  }
@@ -10621,7 +10789,7 @@ function ProfileTab() {
10621
10789
  await auth.refreshProfile();
10622
10790
  toast.success("Profile updated");
10623
10791
  } catch (err) {
10624
- const msg = err instanceof Error ? err.message : "Failed to update profile";
10792
+ const msg = errorMessage(err, { method: "POST" });
10625
10793
  toast.error(msg);
10626
10794
  } finally {
10627
10795
  setSaving(false);
@@ -10654,7 +10822,7 @@ function ProfileTab() {
10654
10822
  toast.success("Profile image updated");
10655
10823
  } catch (err) {
10656
10824
  setImagePreview(void 0);
10657
- const msg = err instanceof Error ? err.message : "Failed to update profile image";
10825
+ const msg = errorMessage(err, { method: "POST" });
10658
10826
  toast.error(msg);
10659
10827
  } finally {
10660
10828
  URL.revokeObjectURL(localPreviewUrl);
@@ -10674,7 +10842,7 @@ function ProfileTab() {
10674
10842
  await auth.refreshProfile();
10675
10843
  toast.success("Profile image removed");
10676
10844
  } catch (err) {
10677
- const msg = err instanceof Error ? err.message : "Failed to remove profile image";
10845
+ const msg = errorMessage(err, { method: "POST" });
10678
10846
  toast.error(msg);
10679
10847
  } finally {
10680
10848
  setImageSaving(false);
@@ -10965,9 +11133,9 @@ function groupWorkspaceList(workspaces, organizations = []) {
10965
11133
  });
10966
11134
  }
10967
11135
  }
10968
- const known = new Set(organizations.map((org) => org._id));
11136
+ const known2 = new Set(organizations.map((org) => org._id));
10969
11137
  const ungrouped = workspaces.filter(
10970
- (ws) => !ws.organizationId || !known.has(ws.organizationId)
11138
+ (ws) => !ws.organizationId || !known2.has(ws.organizationId)
10971
11139
  );
10972
11140
  if (ungrouped.length) {
10973
11141
  groups.push({
@@ -11387,7 +11555,7 @@ function SettingsContentInner({
11387
11555
  void refreshWorkspaceList();
11388
11556
  }, [refreshWorkspaceList]);
11389
11557
  const hostListPage = workspace.listPage;
11390
- const listWorkspacePage = useMemo3(
11558
+ const listWorkspacePage = useMemo4(
11391
11559
  () => hostListPage ? async (params) => {
11392
11560
  const result = await hostListPage(params);
11393
11561
  return {
@@ -11518,17 +11686,6 @@ function SettingsContentInner({
11518
11686
  imageRemoved
11519
11687
  });
11520
11688
  };
11521
- const getErrorMessage = (err, fallback) => {
11522
- if (err instanceof Error && err.message.trim()) return err.message;
11523
- if (err && typeof err === "object" && "body" in err && err.body && typeof err.body === "object" && "status" in err.body && err.body.status && typeof err.body.status === "object" && "message" in err.body.status && typeof err.body.status.message === "string") {
11524
- return err.body.status.message;
11525
- }
11526
- return fallback;
11527
- };
11528
- const isSingleAdminError = (message) => {
11529
- const normalized = message.toLowerCase();
11530
- return normalized.includes("single admin") || normalized.includes("only admin");
11531
- };
11532
11689
  const getSettingsPath = (nextWorkspaceId) => `/dashboard/${nextWorkspaceId}/settings`;
11533
11690
  const getNextWorkspaceId = async (excludedWorkspaceId) => {
11534
11691
  try {
@@ -11612,7 +11769,7 @@ function SettingsContentInner({
11612
11769
  toast.success("Workspace updated");
11613
11770
  return true;
11614
11771
  } catch (err) {
11615
- toast.error(getErrorMessage(err, "Failed to update workspace"));
11772
+ toast.error(errorMessage(err, { method: "PATCH" }));
11616
11773
  return false;
11617
11774
  } finally {
11618
11775
  setUpdatingWorkspace(false);
@@ -11680,7 +11837,7 @@ function SettingsContentInner({
11680
11837
  toast.success("Workspace created");
11681
11838
  return true;
11682
11839
  } catch (err) {
11683
- const msg = err instanceof Error ? err.message : "Failed to create workspace";
11840
+ const msg = errorMessage(err, { method: "POST" });
11684
11841
  toast.error(msg);
11685
11842
  return false;
11686
11843
  } finally {
@@ -11727,7 +11884,7 @@ function SettingsContentInner({
11727
11884
  toast.success("Workspace updated");
11728
11885
  return true;
11729
11886
  } catch (err) {
11730
- const msg = err instanceof Error ? err.message : "Failed to update workspace";
11887
+ const msg = errorMessage(err, { method: "POST" });
11731
11888
  toast.error(msg);
11732
11889
  return false;
11733
11890
  } finally {
@@ -12203,11 +12360,10 @@ function SettingsContentInner({
12203
12360
  setLeavingWorkspace(null);
12204
12361
  await completeWorkspaceRemoval(wsId, `You left ${wsName}`);
12205
12362
  } catch (err) {
12206
- const message = getErrorMessage(err, "Failed to leave workspace");
12207
- if (isSingleAdminError(message)) {
12363
+ if (isOnlyWorkspaceAdmin(err)) {
12208
12364
  toast.error("You are the only admin. Promote another admin before leaving.");
12209
12365
  } else {
12210
- toast.error(message);
12366
+ toast.error(errorMessage(err, { method: "POST" }));
12211
12367
  }
12212
12368
  }
12213
12369
  },
@@ -12237,7 +12393,7 @@ function SettingsContentInner({
12237
12393
  setDeletingWorkspace(null);
12238
12394
  await completeWorkspaceRemoval(wsId, `Workspace ${wsName} deleted`);
12239
12395
  } catch (err) {
12240
- toast.error(getErrorMessage(err, "Failed to delete workspace"));
12396
+ toast.error(errorMessage(err, { method: "DELETE" }));
12241
12397
  }
12242
12398
  }
12243
12399
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/settings",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -36,5 +36,8 @@
36
36
  "react-dom": "^19.0.0",
37
37
  "tsup": "^8.0.0",
38
38
  "typescript": "^5.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@streamoid/errors": "0.1.0"
39
42
  }
40
43
  }