@streamoid/settings 0.7.6 → 0.7.7

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.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
  }
@@ -6355,9 +6509,7 @@ function OrganizationContent({
6355
6509
  await workspaceAdapter.refresh?.().catch(() => void 0);
6356
6510
  await fetchData();
6357
6511
  } catch (err) {
6358
- toast.error(
6359
- err instanceof Error ? err.message : `Couldn't join ${target.name}`
6360
- );
6512
+ toast.error(errorMessage(err, { method: "POST" }));
6361
6513
  } finally {
6362
6514
  setJoiningId(null);
6363
6515
  }
@@ -6370,7 +6522,7 @@ function OrganizationContent({
6370
6522
  toast.success("Organization updated");
6371
6523
  return true;
6372
6524
  } catch (err) {
6373
- toast.error(err instanceof Error ? err.message : "Failed to update organization");
6525
+ toast.error(errorMessage(err, { method: "POST" }));
6374
6526
  return false;
6375
6527
  }
6376
6528
  };
@@ -6381,7 +6533,7 @@ function OrganizationContent({
6381
6533
  toast.success("Organization deleted");
6382
6534
  return true;
6383
6535
  } catch (err) {
6384
- toast.error(err instanceof Error ? err.message : "Failed to delete organization");
6536
+ toast.error(errorMessage(err, { method: "POST" }));
6385
6537
  return false;
6386
6538
  }
6387
6539
  };
@@ -6413,7 +6565,7 @@ function OrganizationContent({
6413
6565
  toast.success("Member updated");
6414
6566
  await fetchData();
6415
6567
  } catch (err) {
6416
- toast.error(err instanceof Error ? err.message : "Failed to update member");
6568
+ toast.error(errorMessage(err, { method: "POST" }));
6417
6569
  }
6418
6570
  closeModal();
6419
6571
  };
@@ -6443,7 +6595,7 @@ function OrganizationContent({
6443
6595
  toast.success("Member removed");
6444
6596
  await fetchData();
6445
6597
  } catch (err) {
6446
- toast.error(err instanceof Error ? err.message : "Failed to remove member");
6598
+ toast.error(errorMessage(err, { method: "POST" }));
6447
6599
  }
6448
6600
  closeModal();
6449
6601
  };
@@ -6455,7 +6607,7 @@ function OrganizationContent({
6455
6607
  );
6456
6608
  await fetchData();
6457
6609
  } catch (err) {
6458
- toast.error(err instanceof Error ? err.message : "Failed to approve request");
6610
+ toast.error(errorMessage(err, { method: "POST" }));
6459
6611
  }
6460
6612
  };
6461
6613
  const handleRejectJoinRequest = async (request) => {
@@ -6464,7 +6616,7 @@ function OrganizationContent({
6464
6616
  toast.success("Request rejected");
6465
6617
  await fetchData();
6466
6618
  } catch (err) {
6467
- toast.error(err instanceof Error ? err.message : "Failed to reject request");
6619
+ toast.error(errorMessage(err, { method: "POST" }));
6468
6620
  }
6469
6621
  };
6470
6622
  if (!current) {
@@ -6694,6 +6846,21 @@ function OrganizationContent({
6694
6846
  ) });
6695
6847
  }
6696
6848
 
6849
+ // src/billing-errors.ts
6850
+ function isBillingWorkspaceMissing(failure) {
6851
+ const seen = /* @__PURE__ */ new Set();
6852
+ const visit = (value, depth) => {
6853
+ if (depth > 4 || value == null || seen.has(value)) return false;
6854
+ if (typeof value === "string") return /workspace.*not found/i.test(value);
6855
+ if (typeof value !== "object") return false;
6856
+ seen.add(value);
6857
+ const error = value;
6858
+ if (error.code === "billing.workspace_not_found") return true;
6859
+ return [error.message, error.error, error.data, error.body, error.cause, error.publicError].some((child) => visit(child, depth + 1));
6860
+ };
6861
+ return visit(failure, 0);
6862
+ }
6863
+
6697
6864
  // src/billing-content.tsx
6698
6865
  var import_react15 = require("react");
6699
6866
  var import_ui9 = require("@streamoid/ui");
@@ -8836,8 +9003,8 @@ function BillingContent({
8836
9003
  toast.success("Plan update submitted");
8837
9004
  await fetchBillingOverview();
8838
9005
  } catch (err) {
8839
- const message = err instanceof Error ? err.message : "Plan update failed";
8840
- const shouldCreateWorkspace = /workspace.*not found/i.test(message);
9006
+ const message = errorMessage(err, { method: "POST" });
9007
+ const shouldCreateWorkspace = isBillingWorkspaceMissing(err);
8841
9008
  if (shouldCreateWorkspace) {
8842
9009
  const created = await ensureWorkspaceOnBilling();
8843
9010
  if (created && api.updateSubscription) {
@@ -8890,7 +9057,7 @@ function BillingContent({
8890
9057
  toast.error("Unable to cancel downgrade");
8891
9058
  }
8892
9059
  } catch (err) {
8893
- const msg = err instanceof Error ? err.message : "Failed to cancel downgrade";
9060
+ const msg = errorMessage(err, { method: "POST" });
8894
9061
  toast.error(msg);
8895
9062
  } finally {
8896
9063
  setPlanActionInFlight(null);
@@ -9081,7 +9248,7 @@ function BillingContent({
9081
9248
  setShowBuyCreditsModal(false);
9082
9249
  await fetchBillingOverview();
9083
9250
  } catch (err) {
9084
- const message = err instanceof Error ? err.message : "Failed to create top-up session";
9251
+ const message = errorMessage(err, { method: "POST" });
9085
9252
  toast.error(message);
9086
9253
  } finally {
9087
9254
  setBuyCreditsInFlight(false);
@@ -9412,7 +9579,7 @@ function CreateTokenModal({ onClose, onCreated }) {
9412
9579
  const data = await api.createToken({ name: name.trim(), expiry });
9413
9580
  onCreated(data);
9414
9581
  } catch (err) {
9415
- toast.error(err instanceof Error ? err.message : "Failed to create token.");
9582
+ toast.error(errorMessage(err, { method: "POST" }));
9416
9583
  } finally {
9417
9584
  setLoading(false);
9418
9585
  }
@@ -9767,7 +9934,7 @@ function TokensContent({
9767
9934
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9768
9935
  }
9769
9936
  } catch (err) {
9770
- toast.error(err instanceof Error ? err.message : "Failed to revoke token.");
9937
+ toast.error(errorMessage(err, { method: "POST" }));
9771
9938
  } finally {
9772
9939
  setDeletingId(null);
9773
9940
  }
@@ -9779,7 +9946,7 @@ function TokensContent({
9779
9946
  toast.success("Token permanently deleted.");
9780
9947
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9781
9948
  } catch (err) {
9782
- toast.error(err instanceof Error ? err.message : "Failed to delete token.");
9949
+ toast.error(errorMessage(err, { method: "POST" }));
9783
9950
  } finally {
9784
9951
  setDeletingId(null);
9785
9952
  }
@@ -10617,7 +10784,7 @@ function ProfileTab() {
10617
10784
  await auth.refreshProfile();
10618
10785
  toast.success("Profile updated");
10619
10786
  } catch (err) {
10620
- const msg = err instanceof Error ? err.message : "Failed to update profile";
10787
+ const msg = errorMessage(err, { method: "POST" });
10621
10788
  toast.error(msg);
10622
10789
  } finally {
10623
10790
  setSaving(false);
@@ -10650,7 +10817,7 @@ function ProfileTab() {
10650
10817
  toast.success("Profile image updated");
10651
10818
  } catch (err) {
10652
10819
  setImagePreview(void 0);
10653
- const msg = err instanceof Error ? err.message : "Failed to update profile image";
10820
+ const msg = errorMessage(err, { method: "POST" });
10654
10821
  toast.error(msg);
10655
10822
  } finally {
10656
10823
  URL.revokeObjectURL(localPreviewUrl);
@@ -10670,7 +10837,7 @@ function ProfileTab() {
10670
10837
  await auth.refreshProfile();
10671
10838
  toast.success("Profile image removed");
10672
10839
  } catch (err) {
10673
- const msg = err instanceof Error ? err.message : "Failed to remove profile image";
10840
+ const msg = errorMessage(err, { method: "POST" });
10674
10841
  toast.error(msg);
10675
10842
  } finally {
10676
10843
  setImageSaving(false);
@@ -10961,9 +11128,9 @@ function groupWorkspaceList(workspaces, organizations = []) {
10961
11128
  });
10962
11129
  }
10963
11130
  }
10964
- const known = new Set(organizations.map((org) => org._id));
11131
+ const known2 = new Set(organizations.map((org) => org._id));
10965
11132
  const ungrouped = workspaces.filter(
10966
- (ws) => !ws.organizationId || !known.has(ws.organizationId)
11133
+ (ws) => !ws.organizationId || !known2.has(ws.organizationId)
10967
11134
  );
10968
11135
  if (ungrouped.length) {
10969
11136
  groups.push({
@@ -11514,17 +11681,6 @@ function SettingsContentInner({
11514
11681
  imageRemoved
11515
11682
  });
11516
11683
  };
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
11684
  const getSettingsPath = (nextWorkspaceId) => `/dashboard/${nextWorkspaceId}/settings`;
11529
11685
  const getNextWorkspaceId = async (excludedWorkspaceId) => {
11530
11686
  try {
@@ -11608,7 +11764,7 @@ function SettingsContentInner({
11608
11764
  toast.success("Workspace updated");
11609
11765
  return true;
11610
11766
  } catch (err) {
11611
- toast.error(getErrorMessage(err, "Failed to update workspace"));
11767
+ toast.error(errorMessage(err, { method: "PATCH" }));
11612
11768
  return false;
11613
11769
  } finally {
11614
11770
  setUpdatingWorkspace(false);
@@ -11676,7 +11832,7 @@ function SettingsContentInner({
11676
11832
  toast.success("Workspace created");
11677
11833
  return true;
11678
11834
  } catch (err) {
11679
- const msg = err instanceof Error ? err.message : "Failed to create workspace";
11835
+ const msg = errorMessage(err, { method: "POST" });
11680
11836
  toast.error(msg);
11681
11837
  return false;
11682
11838
  } finally {
@@ -11723,7 +11879,7 @@ function SettingsContentInner({
11723
11879
  toast.success("Workspace updated");
11724
11880
  return true;
11725
11881
  } catch (err) {
11726
- const msg = err instanceof Error ? err.message : "Failed to update workspace";
11882
+ const msg = errorMessage(err, { method: "POST" });
11727
11883
  toast.error(msg);
11728
11884
  return false;
11729
11885
  } finally {
@@ -12199,11 +12355,10 @@ function SettingsContentInner({
12199
12355
  setLeavingWorkspace(null);
12200
12356
  await completeWorkspaceRemoval(wsId, `You left ${wsName}`);
12201
12357
  } catch (err) {
12202
- const message = getErrorMessage(err, "Failed to leave workspace");
12203
- if (isSingleAdminError(message)) {
12358
+ if (isOnlyWorkspaceAdmin(err)) {
12204
12359
  toast.error("You are the only admin. Promote another admin before leaving.");
12205
12360
  } else {
12206
- toast.error(message);
12361
+ toast.error(errorMessage(err, { method: "POST" }));
12207
12362
  }
12208
12363
  }
12209
12364
  },
@@ -12233,7 +12388,7 @@ function SettingsContentInner({
12233
12388
  setDeletingWorkspace(null);
12234
12389
  await completeWorkspaceRemoval(wsId, `Workspace ${wsName} deleted`);
12235
12390
  } catch (err) {
12236
- toast.error(getErrorMessage(err, "Failed to delete workspace"));
12391
+ toast.error(errorMessage(err, { method: "DELETE" }));
12237
12392
  }
12238
12393
  }
12239
12394
  }
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
  }
@@ -6332,9 +6486,7 @@ function OrganizationContent({
6332
6486
  await workspaceAdapter.refresh?.().catch(() => void 0);
6333
6487
  await fetchData();
6334
6488
  } catch (err) {
6335
- toast.error(
6336
- err instanceof Error ? err.message : `Couldn't join ${target.name}`
6337
- );
6489
+ toast.error(errorMessage(err, { method: "POST" }));
6338
6490
  } finally {
6339
6491
  setJoiningId(null);
6340
6492
  }
@@ -6347,7 +6499,7 @@ function OrganizationContent({
6347
6499
  toast.success("Organization updated");
6348
6500
  return true;
6349
6501
  } catch (err) {
6350
- toast.error(err instanceof Error ? err.message : "Failed to update organization");
6502
+ toast.error(errorMessage(err, { method: "POST" }));
6351
6503
  return false;
6352
6504
  }
6353
6505
  };
@@ -6358,7 +6510,7 @@ function OrganizationContent({
6358
6510
  toast.success("Organization deleted");
6359
6511
  return true;
6360
6512
  } catch (err) {
6361
- toast.error(err instanceof Error ? err.message : "Failed to delete organization");
6513
+ toast.error(errorMessage(err, { method: "POST" }));
6362
6514
  return false;
6363
6515
  }
6364
6516
  };
@@ -6390,7 +6542,7 @@ function OrganizationContent({
6390
6542
  toast.success("Member updated");
6391
6543
  await fetchData();
6392
6544
  } catch (err) {
6393
- toast.error(err instanceof Error ? err.message : "Failed to update member");
6545
+ toast.error(errorMessage(err, { method: "POST" }));
6394
6546
  }
6395
6547
  closeModal();
6396
6548
  };
@@ -6420,7 +6572,7 @@ function OrganizationContent({
6420
6572
  toast.success("Member removed");
6421
6573
  await fetchData();
6422
6574
  } catch (err) {
6423
- toast.error(err instanceof Error ? err.message : "Failed to remove member");
6575
+ toast.error(errorMessage(err, { method: "POST" }));
6424
6576
  }
6425
6577
  closeModal();
6426
6578
  };
@@ -6432,7 +6584,7 @@ function OrganizationContent({
6432
6584
  );
6433
6585
  await fetchData();
6434
6586
  } catch (err) {
6435
- toast.error(err instanceof Error ? err.message : "Failed to approve request");
6587
+ toast.error(errorMessage(err, { method: "POST" }));
6436
6588
  }
6437
6589
  };
6438
6590
  const handleRejectJoinRequest = async (request) => {
@@ -6441,7 +6593,7 @@ function OrganizationContent({
6441
6593
  toast.success("Request rejected");
6442
6594
  await fetchData();
6443
6595
  } catch (err) {
6444
- toast.error(err instanceof Error ? err.message : "Failed to reject request");
6596
+ toast.error(errorMessage(err, { method: "POST" }));
6445
6597
  }
6446
6598
  };
6447
6599
  if (!current) {
@@ -6671,12 +6823,27 @@ function OrganizationContent({
6671
6823
  ) });
6672
6824
  }
6673
6825
 
6826
+ // src/billing-errors.ts
6827
+ function isBillingWorkspaceMissing(failure) {
6828
+ const seen = /* @__PURE__ */ new Set();
6829
+ const visit = (value, depth) => {
6830
+ if (depth > 4 || value == null || seen.has(value)) return false;
6831
+ if (typeof value === "string") return /workspace.*not found/i.test(value);
6832
+ if (typeof value !== "object") return false;
6833
+ seen.add(value);
6834
+ const error = value;
6835
+ if (error.code === "billing.workspace_not_found") return true;
6836
+ return [error.message, error.error, error.data, error.body, error.cause, error.publicError].some((child) => visit(child, depth + 1));
6837
+ };
6838
+ return visit(failure, 0);
6839
+ }
6840
+
6674
6841
  // src/billing-content.tsx
6675
6842
  import {
6676
6843
  useState as useState13,
6677
6844
  useEffect as useEffect11,
6678
6845
  useCallback as useCallback5,
6679
- useMemo as useMemo2,
6846
+ useMemo as useMemo3,
6680
6847
  useRef as useRef6
6681
6848
  } from "react";
6682
6849
  import {
@@ -6782,7 +6949,7 @@ var PLAN_TAGLINES = {
6782
6949
  };
6783
6950
 
6784
6951
  // src/export-date-modal.tsx
6785
- import { useState as useState11, useMemo, useCallback as useCallback4 } from "react";
6952
+ import { useState as useState11, useMemo as useMemo2, useCallback as useCallback4 } from "react";
6786
6953
  import { SiconClose as SiconClose4, SiconLeft, SiconRight } from "@streamoid/icons";
6787
6954
  import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
6788
6955
  var PRIMARY_COLOR = "var(--alias-text---icons-primary)";
@@ -6837,7 +7004,7 @@ function ExportDateModal({
6837
7004
  const [rangeEnd, setRangeEnd] = useState11(null);
6838
7005
  const isFuture = (date) => date.getTime() > today.getTime();
6839
7006
  const isCurrentMonthView = viewMonth === now.getMonth() && viewYear === now.getFullYear();
6840
- const cells = useMemo(() => {
7007
+ const cells = useMemo2(() => {
6841
7008
  const result = [];
6842
7009
  const daysInMonth = getDaysInMonth(viewYear, viewMonth);
6843
7010
  const firstDay = new Date(viewYear, viewMonth, 1).getDay();
@@ -8000,7 +8167,7 @@ function PlansView({
8000
8167
  const workspaceCurrencySymbol = getCurrencySymbolByCountry(
8001
8168
  workspace?.country
8002
8169
  );
8003
- const allPlans = useMemo2(() => {
8170
+ const allPlans = useMemo3(() => {
8004
8171
  if (products.length > 0) {
8005
8172
  return products.flatMap(
8006
8173
  (product) => product.prices.filter((price) => Boolean(price.metadata?.credits)).map((price) => ({
@@ -8018,7 +8185,7 @@ function PlansView({
8018
8185
  }
8019
8186
  return [];
8020
8187
  }, [products, currentPlanId]);
8021
- const sortedPlans = useMemo2(
8188
+ const sortedPlans = useMemo3(
8022
8189
  () => [...allPlans].sort((a, b) => a.amount - b.amount),
8023
8190
  [allPlans]
8024
8191
  );
@@ -8834,8 +9001,8 @@ function BillingContent({
8834
9001
  toast.success("Plan update submitted");
8835
9002
  await fetchBillingOverview();
8836
9003
  } catch (err) {
8837
- const message = err instanceof Error ? err.message : "Plan update failed";
8838
- const shouldCreateWorkspace = /workspace.*not found/i.test(message);
9004
+ const message = errorMessage(err, { method: "POST" });
9005
+ const shouldCreateWorkspace = isBillingWorkspaceMissing(err);
8839
9006
  if (shouldCreateWorkspace) {
8840
9007
  const created = await ensureWorkspaceOnBilling();
8841
9008
  if (created && api.updateSubscription) {
@@ -8888,7 +9055,7 @@ function BillingContent({
8888
9055
  toast.error("Unable to cancel downgrade");
8889
9056
  }
8890
9057
  } catch (err) {
8891
- const msg = err instanceof Error ? err.message : "Failed to cancel downgrade";
9058
+ const msg = errorMessage(err, { method: "POST" });
8892
9059
  toast.error(msg);
8893
9060
  } finally {
8894
9061
  setPlanActionInFlight(null);
@@ -9079,7 +9246,7 @@ function BillingContent({
9079
9246
  setShowBuyCreditsModal(false);
9080
9247
  await fetchBillingOverview();
9081
9248
  } catch (err) {
9082
- const message = err instanceof Error ? err.message : "Failed to create top-up session";
9249
+ const message = errorMessage(err, { method: "POST" });
9083
9250
  toast.error(message);
9084
9251
  } finally {
9085
9252
  setBuyCreditsInFlight(false);
@@ -9416,7 +9583,7 @@ function CreateTokenModal({ onClose, onCreated }) {
9416
9583
  const data = await api.createToken({ name: name.trim(), expiry });
9417
9584
  onCreated(data);
9418
9585
  } catch (err) {
9419
- toast.error(err instanceof Error ? err.message : "Failed to create token.");
9586
+ toast.error(errorMessage(err, { method: "POST" }));
9420
9587
  } finally {
9421
9588
  setLoading(false);
9422
9589
  }
@@ -9771,7 +9938,7 @@ function TokensContent({
9771
9938
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9772
9939
  }
9773
9940
  } catch (err) {
9774
- toast.error(err instanceof Error ? err.message : "Failed to revoke token.");
9941
+ toast.error(errorMessage(err, { method: "POST" }));
9775
9942
  } finally {
9776
9943
  setDeletingId(null);
9777
9944
  }
@@ -9783,7 +9950,7 @@ function TokensContent({
9783
9950
  toast.success("Token permanently deleted.");
9784
9951
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9785
9952
  } catch (err) {
9786
- toast.error(err instanceof Error ? err.message : "Failed to delete token.");
9953
+ toast.error(errorMessage(err, { method: "POST" }));
9787
9954
  } finally {
9788
9955
  setDeletingId(null);
9789
9956
  }
@@ -10621,7 +10788,7 @@ function ProfileTab() {
10621
10788
  await auth.refreshProfile();
10622
10789
  toast.success("Profile updated");
10623
10790
  } catch (err) {
10624
- const msg = err instanceof Error ? err.message : "Failed to update profile";
10791
+ const msg = errorMessage(err, { method: "POST" });
10625
10792
  toast.error(msg);
10626
10793
  } finally {
10627
10794
  setSaving(false);
@@ -10654,7 +10821,7 @@ function ProfileTab() {
10654
10821
  toast.success("Profile image updated");
10655
10822
  } catch (err) {
10656
10823
  setImagePreview(void 0);
10657
- const msg = err instanceof Error ? err.message : "Failed to update profile image";
10824
+ const msg = errorMessage(err, { method: "POST" });
10658
10825
  toast.error(msg);
10659
10826
  } finally {
10660
10827
  URL.revokeObjectURL(localPreviewUrl);
@@ -10674,7 +10841,7 @@ function ProfileTab() {
10674
10841
  await auth.refreshProfile();
10675
10842
  toast.success("Profile image removed");
10676
10843
  } catch (err) {
10677
- const msg = err instanceof Error ? err.message : "Failed to remove profile image";
10844
+ const msg = errorMessage(err, { method: "POST" });
10678
10845
  toast.error(msg);
10679
10846
  } finally {
10680
10847
  setImageSaving(false);
@@ -10965,9 +11132,9 @@ function groupWorkspaceList(workspaces, organizations = []) {
10965
11132
  });
10966
11133
  }
10967
11134
  }
10968
- const known = new Set(organizations.map((org) => org._id));
11135
+ const known2 = new Set(organizations.map((org) => org._id));
10969
11136
  const ungrouped = workspaces.filter(
10970
- (ws) => !ws.organizationId || !known.has(ws.organizationId)
11137
+ (ws) => !ws.organizationId || !known2.has(ws.organizationId)
10971
11138
  );
10972
11139
  if (ungrouped.length) {
10973
11140
  groups.push({
@@ -11387,7 +11554,7 @@ function SettingsContentInner({
11387
11554
  void refreshWorkspaceList();
11388
11555
  }, [refreshWorkspaceList]);
11389
11556
  const hostListPage = workspace.listPage;
11390
- const listWorkspacePage = useMemo3(
11557
+ const listWorkspacePage = useMemo4(
11391
11558
  () => hostListPage ? async (params) => {
11392
11559
  const result = await hostListPage(params);
11393
11560
  return {
@@ -11518,17 +11685,6 @@ function SettingsContentInner({
11518
11685
  imageRemoved
11519
11686
  });
11520
11687
  };
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
11688
  const getSettingsPath = (nextWorkspaceId) => `/dashboard/${nextWorkspaceId}/settings`;
11533
11689
  const getNextWorkspaceId = async (excludedWorkspaceId) => {
11534
11690
  try {
@@ -11612,7 +11768,7 @@ function SettingsContentInner({
11612
11768
  toast.success("Workspace updated");
11613
11769
  return true;
11614
11770
  } catch (err) {
11615
- toast.error(getErrorMessage(err, "Failed to update workspace"));
11771
+ toast.error(errorMessage(err, { method: "PATCH" }));
11616
11772
  return false;
11617
11773
  } finally {
11618
11774
  setUpdatingWorkspace(false);
@@ -11680,7 +11836,7 @@ function SettingsContentInner({
11680
11836
  toast.success("Workspace created");
11681
11837
  return true;
11682
11838
  } catch (err) {
11683
- const msg = err instanceof Error ? err.message : "Failed to create workspace";
11839
+ const msg = errorMessage(err, { method: "POST" });
11684
11840
  toast.error(msg);
11685
11841
  return false;
11686
11842
  } finally {
@@ -11727,7 +11883,7 @@ function SettingsContentInner({
11727
11883
  toast.success("Workspace updated");
11728
11884
  return true;
11729
11885
  } catch (err) {
11730
- const msg = err instanceof Error ? err.message : "Failed to update workspace";
11886
+ const msg = errorMessage(err, { method: "POST" });
11731
11887
  toast.error(msg);
11732
11888
  return false;
11733
11889
  } finally {
@@ -12203,11 +12359,10 @@ function SettingsContentInner({
12203
12359
  setLeavingWorkspace(null);
12204
12360
  await completeWorkspaceRemoval(wsId, `You left ${wsName}`);
12205
12361
  } catch (err) {
12206
- const message = getErrorMessage(err, "Failed to leave workspace");
12207
- if (isSingleAdminError(message)) {
12362
+ if (isOnlyWorkspaceAdmin(err)) {
12208
12363
  toast.error("You are the only admin. Promote another admin before leaving.");
12209
12364
  } else {
12210
- toast.error(message);
12365
+ toast.error(errorMessage(err, { method: "POST" }));
12211
12366
  }
12212
12367
  }
12213
12368
  },
@@ -12237,7 +12392,7 @@ function SettingsContentInner({
12237
12392
  setDeletingWorkspace(null);
12238
12393
  await completeWorkspaceRemoval(wsId, `Workspace ${wsName} deleted`);
12239
12394
  } catch (err) {
12240
- toast.error(getErrorMessage(err, "Failed to delete workspace"));
12395
+ toast.error(errorMessage(err, { method: "DELETE" }));
12241
12396
  }
12242
12397
  }
12243
12398
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/settings",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
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
  }