@streamoid/settings 0.7.5 → 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/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
  }
@@ -6110,7 +6264,11 @@ function MemberRow({
6110
6264
  }
6111
6265
  );
6112
6266
  }
6113
- function WorkspaceRow({ workspace }) {
6267
+ function WorkspaceRow({
6268
+ workspace,
6269
+ joining = false,
6270
+ onJoin
6271
+ }) {
6114
6272
  return /* @__PURE__ */ jsxs14(
6115
6273
  "div",
6116
6274
  {
@@ -6146,80 +6304,19 @@ function WorkspaceRow({ workspace }) {
6146
6304
  }
6147
6305
  )
6148
6306
  ] }),
6149
- /* @__PURE__ */ jsx17(VisibilityBadge, { visibility: workspace.orgVisibility })
6150
- ]
6151
- }
6152
- );
6153
- }
6154
- function JoinableWorkspaceRow({
6155
- workspace,
6156
- joining,
6157
- onJoin
6158
- }) {
6159
- return /* @__PURE__ */ jsxs14(
6160
- "div",
6161
- {
6162
- className: "flex items-center self-stretch shrink-0",
6163
- style: {
6164
- borderBottom: `1px solid ${T11.borderSubtle}`,
6165
- padding: `${T11.spXl} ${T11.sp3xl}`,
6166
- gap: T11.sp6xl
6167
- },
6168
- children: [
6169
- /* @__PURE__ */ jsxs14("div", { className: "flex items-center min-w-0", style: { flex: 1, gap: T11.spMd }, children: [
6170
- /* @__PURE__ */ jsx17(
6171
- ProfileAvatar,
6307
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center shrink-0", style: { gap: T11.spMd }, children: [
6308
+ /* @__PURE__ */ jsx17(VisibilityBadge, { visibility: workspace.orgVisibility }),
6309
+ onJoin && /* @__PURE__ */ jsx17(
6310
+ ScButton5,
6172
6311
  {
6173
- name: workspace.name,
6174
- profileImage: workspace.imageUrl,
6175
- size: 28,
6176
- borderRadius: "var(--radius-lg)"
6312
+ text: joining ? "Joining\u2026" : "Join",
6313
+ variant: "secondary",
6314
+ size: "sm",
6315
+ state: joining ? "disabled" : "default",
6316
+ onClick: joining ? void 0 : onJoin
6177
6317
  }
6178
- ),
6179
- /* @__PURE__ */ jsxs14("div", { className: "flex flex-col min-w-0", children: [
6180
- /* @__PURE__ */ jsx17(
6181
- "span",
6182
- {
6183
- className: "truncate",
6184
- style: {
6185
- fontFamily: T11.font,
6186
- fontSize: T11.fsSm,
6187
- fontWeight: 500,
6188
- lineHeight: T11.lhSm,
6189
- color: T11.primary
6190
- },
6191
- children: workspace.name
6192
- }
6193
- ),
6194
- workspace.ownerName && /* @__PURE__ */ jsxs14(
6195
- "span",
6196
- {
6197
- className: "truncate",
6198
- style: {
6199
- fontFamily: T11.font,
6200
- fontSize: T11.fsXs,
6201
- fontWeight: 400,
6202
- lineHeight: T11.lhXs,
6203
- color: T11.muted
6204
- },
6205
- children: [
6206
- "Created by ",
6207
- workspace.ownerName
6208
- ]
6209
- }
6210
- )
6211
- ] })
6212
- ] }),
6213
- /* @__PURE__ */ jsx17(
6214
- ScButton5,
6215
- {
6216
- text: joining ? "Joining\u2026" : "Join",
6217
- variant: "secondary",
6218
- size: "sm",
6219
- state: joining ? "disabled" : "default",
6220
- onClick: joining ? void 0 : onJoin
6221
- }
6222
- )
6318
+ )
6319
+ ] })
6223
6320
  ]
6224
6321
  }
6225
6322
  );
@@ -6347,8 +6444,7 @@ function OrganizationContent({
6347
6444
  const current = organization.current;
6348
6445
  const [members, setMembers] = useState10([]);
6349
6446
  const [workspaces, setWorkspaces] = useState10([]);
6350
- const canJoinShared = current?.role === "member" && !!organization.fetchJoinableWorkspaces && !!organization.joinWorkspaces;
6351
- const [joinable, setJoinable] = useState10([]);
6447
+ const canJoinShared = current?.role === "member" && !!organization.joinWorkspaces;
6352
6448
  const [joiningId, setJoiningId] = useState10(null);
6353
6449
  const [joinRequests, setJoinRequests] = useState10([]);
6354
6450
  const [highestRole, setHighestRole] = useState10("guest");
@@ -6359,20 +6455,12 @@ function OrganizationContent({
6359
6455
  if (!organization.current) return;
6360
6456
  setLoadingData(true);
6361
6457
  try {
6362
- const [
6363
- membersRes,
6364
- workspacesRes,
6365
- joinRequestsRes,
6366
- highestRoleRes,
6367
- joinableRes
6368
- ] = await Promise.all([
6458
+ const [membersRes, workspacesRes, joinRequestsRes, highestRoleRes] = await Promise.all([
6369
6459
  organization.fetchMembers().catch(() => null),
6370
6460
  organization.fetchWorkspaces().catch(() => null),
6371
6461
  organization.fetchJoinRequests().catch(() => null),
6372
- organization.getHighestRole?.().catch(() => null) ?? Promise.resolve(null),
6373
- canJoinShared ? organization.fetchJoinableWorkspaces().catch(() => null) : Promise.resolve(null)
6462
+ organization.getHighestRole?.().catch(() => null) ?? Promise.resolve(null)
6374
6463
  ]);
6375
- if (joinableRes) setJoinable(joinableRes);
6376
6464
  if (highestRoleRes?.role) setHighestRole(highestRoleRes.role);
6377
6465
  if (membersRes) setMembers(membersRes.filter((m) => m?.user));
6378
6466
  if (workspacesRes) setWorkspaces(workspacesRes);
@@ -6381,7 +6469,7 @@ function OrganizationContent({
6381
6469
  } finally {
6382
6470
  setLoadingData(false);
6383
6471
  }
6384
- }, [organization, canJoinShared]);
6472
+ }, [organization]);
6385
6473
  useEffect10(() => {
6386
6474
  fetchData();
6387
6475
  }, [fetchData]);
@@ -6398,9 +6486,7 @@ function OrganizationContent({
6398
6486
  await workspaceAdapter.refresh?.().catch(() => void 0);
6399
6487
  await fetchData();
6400
6488
  } catch (err) {
6401
- toast.error(
6402
- err instanceof Error ? err.message : `Couldn't join ${target.name}`
6403
- );
6489
+ toast.error(errorMessage(err, { method: "POST" }));
6404
6490
  } finally {
6405
6491
  setJoiningId(null);
6406
6492
  }
@@ -6413,7 +6499,7 @@ function OrganizationContent({
6413
6499
  toast.success("Organization updated");
6414
6500
  return true;
6415
6501
  } catch (err) {
6416
- toast.error(err instanceof Error ? err.message : "Failed to update organization");
6502
+ toast.error(errorMessage(err, { method: "POST" }));
6417
6503
  return false;
6418
6504
  }
6419
6505
  };
@@ -6424,7 +6510,7 @@ function OrganizationContent({
6424
6510
  toast.success("Organization deleted");
6425
6511
  return true;
6426
6512
  } catch (err) {
6427
- toast.error(err instanceof Error ? err.message : "Failed to delete organization");
6513
+ toast.error(errorMessage(err, { method: "POST" }));
6428
6514
  return false;
6429
6515
  }
6430
6516
  };
@@ -6456,7 +6542,7 @@ function OrganizationContent({
6456
6542
  toast.success("Member updated");
6457
6543
  await fetchData();
6458
6544
  } catch (err) {
6459
- toast.error(err instanceof Error ? err.message : "Failed to update member");
6545
+ toast.error(errorMessage(err, { method: "POST" }));
6460
6546
  }
6461
6547
  closeModal();
6462
6548
  };
@@ -6486,7 +6572,7 @@ function OrganizationContent({
6486
6572
  toast.success("Member removed");
6487
6573
  await fetchData();
6488
6574
  } catch (err) {
6489
- toast.error(err instanceof Error ? err.message : "Failed to remove member");
6575
+ toast.error(errorMessage(err, { method: "POST" }));
6490
6576
  }
6491
6577
  closeModal();
6492
6578
  };
@@ -6498,7 +6584,7 @@ function OrganizationContent({
6498
6584
  );
6499
6585
  await fetchData();
6500
6586
  } catch (err) {
6501
- toast.error(err instanceof Error ? err.message : "Failed to approve request");
6587
+ toast.error(errorMessage(err, { method: "POST" }));
6502
6588
  }
6503
6589
  };
6504
6590
  const handleRejectJoinRequest = async (request) => {
@@ -6507,7 +6593,7 @@ function OrganizationContent({
6507
6593
  toast.success("Request rejected");
6508
6594
  await fetchData();
6509
6595
  } catch (err) {
6510
- toast.error(err instanceof Error ? err.message : "Failed to reject request");
6596
+ toast.error(errorMessage(err, { method: "POST" }));
6511
6597
  }
6512
6598
  };
6513
6599
  if (!current) {
@@ -6630,21 +6716,12 @@ function OrganizationContent({
6630
6716
  {
6631
6717
  title: "Workspaces in this organization",
6632
6718
  empty: workspaces.length === 0,
6633
- children: workspaces.map((ws) => /* @__PURE__ */ jsx17(WorkspaceRow, { workspace: ws }, ws._id))
6634
- }
6635
- ),
6636
- canJoinShared && /* @__PURE__ */ jsx17(
6637
- Section,
6638
- {
6639
- title: "Shared workspaces you can join",
6640
- empty: joinable.length === 0,
6641
- emptyText: "You're in every shared workspace in this organization",
6642
- children: joinable.map((ws) => /* @__PURE__ */ jsx17(
6643
- JoinableWorkspaceRow,
6719
+ children: workspaces.map((ws) => /* @__PURE__ */ jsx17(
6720
+ WorkspaceRow,
6644
6721
  {
6645
6722
  workspace: ws,
6646
6723
  joining: joiningId === ws._id,
6647
- onJoin: () => void handleJoinWorkspace(ws)
6724
+ onJoin: canJoinShared && ws.joined === false && ws.orgVisibility === "shared" ? () => void handleJoinWorkspace(ws) : void 0
6648
6725
  },
6649
6726
  ws._id
6650
6727
  ))
@@ -6746,12 +6823,27 @@ function OrganizationContent({
6746
6823
  ) });
6747
6824
  }
6748
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
+
6749
6841
  // src/billing-content.tsx
6750
6842
  import {
6751
6843
  useState as useState13,
6752
6844
  useEffect as useEffect11,
6753
6845
  useCallback as useCallback5,
6754
- useMemo as useMemo2,
6846
+ useMemo as useMemo3,
6755
6847
  useRef as useRef6
6756
6848
  } from "react";
6757
6849
  import {
@@ -6857,7 +6949,7 @@ var PLAN_TAGLINES = {
6857
6949
  };
6858
6950
 
6859
6951
  // src/export-date-modal.tsx
6860
- import { useState as useState11, useMemo, useCallback as useCallback4 } from "react";
6952
+ import { useState as useState11, useMemo as useMemo2, useCallback as useCallback4 } from "react";
6861
6953
  import { SiconClose as SiconClose4, SiconLeft, SiconRight } from "@streamoid/icons";
6862
6954
  import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
6863
6955
  var PRIMARY_COLOR = "var(--alias-text---icons-primary)";
@@ -6912,7 +7004,7 @@ function ExportDateModal({
6912
7004
  const [rangeEnd, setRangeEnd] = useState11(null);
6913
7005
  const isFuture = (date) => date.getTime() > today.getTime();
6914
7006
  const isCurrentMonthView = viewMonth === now.getMonth() && viewYear === now.getFullYear();
6915
- const cells = useMemo(() => {
7007
+ const cells = useMemo2(() => {
6916
7008
  const result = [];
6917
7009
  const daysInMonth = getDaysInMonth(viewYear, viewMonth);
6918
7010
  const firstDay = new Date(viewYear, viewMonth, 1).getDay();
@@ -8075,7 +8167,7 @@ function PlansView({
8075
8167
  const workspaceCurrencySymbol = getCurrencySymbolByCountry(
8076
8168
  workspace?.country
8077
8169
  );
8078
- const allPlans = useMemo2(() => {
8170
+ const allPlans = useMemo3(() => {
8079
8171
  if (products.length > 0) {
8080
8172
  return products.flatMap(
8081
8173
  (product) => product.prices.filter((price) => Boolean(price.metadata?.credits)).map((price) => ({
@@ -8093,7 +8185,7 @@ function PlansView({
8093
8185
  }
8094
8186
  return [];
8095
8187
  }, [products, currentPlanId]);
8096
- const sortedPlans = useMemo2(
8188
+ const sortedPlans = useMemo3(
8097
8189
  () => [...allPlans].sort((a, b) => a.amount - b.amount),
8098
8190
  [allPlans]
8099
8191
  );
@@ -8909,8 +9001,8 @@ function BillingContent({
8909
9001
  toast.success("Plan update submitted");
8910
9002
  await fetchBillingOverview();
8911
9003
  } catch (err) {
8912
- const message = err instanceof Error ? err.message : "Plan update failed";
8913
- const shouldCreateWorkspace = /workspace.*not found/i.test(message);
9004
+ const message = errorMessage(err, { method: "POST" });
9005
+ const shouldCreateWorkspace = isBillingWorkspaceMissing(err);
8914
9006
  if (shouldCreateWorkspace) {
8915
9007
  const created = await ensureWorkspaceOnBilling();
8916
9008
  if (created && api.updateSubscription) {
@@ -8963,7 +9055,7 @@ function BillingContent({
8963
9055
  toast.error("Unable to cancel downgrade");
8964
9056
  }
8965
9057
  } catch (err) {
8966
- const msg = err instanceof Error ? err.message : "Failed to cancel downgrade";
9058
+ const msg = errorMessage(err, { method: "POST" });
8967
9059
  toast.error(msg);
8968
9060
  } finally {
8969
9061
  setPlanActionInFlight(null);
@@ -9154,7 +9246,7 @@ function BillingContent({
9154
9246
  setShowBuyCreditsModal(false);
9155
9247
  await fetchBillingOverview();
9156
9248
  } catch (err) {
9157
- const message = err instanceof Error ? err.message : "Failed to create top-up session";
9249
+ const message = errorMessage(err, { method: "POST" });
9158
9250
  toast.error(message);
9159
9251
  } finally {
9160
9252
  setBuyCreditsInFlight(false);
@@ -9491,7 +9583,7 @@ function CreateTokenModal({ onClose, onCreated }) {
9491
9583
  const data = await api.createToken({ name: name.trim(), expiry });
9492
9584
  onCreated(data);
9493
9585
  } catch (err) {
9494
- toast.error(err instanceof Error ? err.message : "Failed to create token.");
9586
+ toast.error(errorMessage(err, { method: "POST" }));
9495
9587
  } finally {
9496
9588
  setLoading(false);
9497
9589
  }
@@ -9846,7 +9938,7 @@ function TokensContent({
9846
9938
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9847
9939
  }
9848
9940
  } catch (err) {
9849
- toast.error(err instanceof Error ? err.message : "Failed to revoke token.");
9941
+ toast.error(errorMessage(err, { method: "POST" }));
9850
9942
  } finally {
9851
9943
  setDeletingId(null);
9852
9944
  }
@@ -9858,7 +9950,7 @@ function TokensContent({
9858
9950
  toast.success("Token permanently deleted.");
9859
9951
  setTokens((prev) => prev.filter((t) => t._id !== tokenId));
9860
9952
  } catch (err) {
9861
- toast.error(err instanceof Error ? err.message : "Failed to delete token.");
9953
+ toast.error(errorMessage(err, { method: "POST" }));
9862
9954
  } finally {
9863
9955
  setDeletingId(null);
9864
9956
  }
@@ -10696,7 +10788,7 @@ function ProfileTab() {
10696
10788
  await auth.refreshProfile();
10697
10789
  toast.success("Profile updated");
10698
10790
  } catch (err) {
10699
- const msg = err instanceof Error ? err.message : "Failed to update profile";
10791
+ const msg = errorMessage(err, { method: "POST" });
10700
10792
  toast.error(msg);
10701
10793
  } finally {
10702
10794
  setSaving(false);
@@ -10729,7 +10821,7 @@ function ProfileTab() {
10729
10821
  toast.success("Profile image updated");
10730
10822
  } catch (err) {
10731
10823
  setImagePreview(void 0);
10732
- const msg = err instanceof Error ? err.message : "Failed to update profile image";
10824
+ const msg = errorMessage(err, { method: "POST" });
10733
10825
  toast.error(msg);
10734
10826
  } finally {
10735
10827
  URL.revokeObjectURL(localPreviewUrl);
@@ -10749,7 +10841,7 @@ function ProfileTab() {
10749
10841
  await auth.refreshProfile();
10750
10842
  toast.success("Profile image removed");
10751
10843
  } catch (err) {
10752
- const msg = err instanceof Error ? err.message : "Failed to remove profile image";
10844
+ const msg = errorMessage(err, { method: "POST" });
10753
10845
  toast.error(msg);
10754
10846
  } finally {
10755
10847
  setImageSaving(false);
@@ -11040,9 +11132,9 @@ function groupWorkspaceList(workspaces, organizations = []) {
11040
11132
  });
11041
11133
  }
11042
11134
  }
11043
- const known = new Set(organizations.map((org) => org._id));
11135
+ const known2 = new Set(organizations.map((org) => org._id));
11044
11136
  const ungrouped = workspaces.filter(
11045
- (ws) => !ws.organizationId || !known.has(ws.organizationId)
11137
+ (ws) => !ws.organizationId || !known2.has(ws.organizationId)
11046
11138
  );
11047
11139
  if (ungrouped.length) {
11048
11140
  groups.push({
@@ -11462,7 +11554,7 @@ function SettingsContentInner({
11462
11554
  void refreshWorkspaceList();
11463
11555
  }, [refreshWorkspaceList]);
11464
11556
  const hostListPage = workspace.listPage;
11465
- const listWorkspacePage = useMemo3(
11557
+ const listWorkspacePage = useMemo4(
11466
11558
  () => hostListPage ? async (params) => {
11467
11559
  const result = await hostListPage(params);
11468
11560
  return {
@@ -11593,17 +11685,6 @@ function SettingsContentInner({
11593
11685
  imageRemoved
11594
11686
  });
11595
11687
  };
11596
- const getErrorMessage = (err, fallback) => {
11597
- if (err instanceof Error && err.message.trim()) return err.message;
11598
- 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") {
11599
- return err.body.status.message;
11600
- }
11601
- return fallback;
11602
- };
11603
- const isSingleAdminError = (message) => {
11604
- const normalized = message.toLowerCase();
11605
- return normalized.includes("single admin") || normalized.includes("only admin");
11606
- };
11607
11688
  const getSettingsPath = (nextWorkspaceId) => `/dashboard/${nextWorkspaceId}/settings`;
11608
11689
  const getNextWorkspaceId = async (excludedWorkspaceId) => {
11609
11690
  try {
@@ -11687,7 +11768,7 @@ function SettingsContentInner({
11687
11768
  toast.success("Workspace updated");
11688
11769
  return true;
11689
11770
  } catch (err) {
11690
- toast.error(getErrorMessage(err, "Failed to update workspace"));
11771
+ toast.error(errorMessage(err, { method: "PATCH" }));
11691
11772
  return false;
11692
11773
  } finally {
11693
11774
  setUpdatingWorkspace(false);
@@ -11755,7 +11836,7 @@ function SettingsContentInner({
11755
11836
  toast.success("Workspace created");
11756
11837
  return true;
11757
11838
  } catch (err) {
11758
- const msg = err instanceof Error ? err.message : "Failed to create workspace";
11839
+ const msg = errorMessage(err, { method: "POST" });
11759
11840
  toast.error(msg);
11760
11841
  return false;
11761
11842
  } finally {
@@ -11802,7 +11883,7 @@ function SettingsContentInner({
11802
11883
  toast.success("Workspace updated");
11803
11884
  return true;
11804
11885
  } catch (err) {
11805
- const msg = err instanceof Error ? err.message : "Failed to update workspace";
11886
+ const msg = errorMessage(err, { method: "POST" });
11806
11887
  toast.error(msg);
11807
11888
  return false;
11808
11889
  } finally {
@@ -12278,11 +12359,10 @@ function SettingsContentInner({
12278
12359
  setLeavingWorkspace(null);
12279
12360
  await completeWorkspaceRemoval(wsId, `You left ${wsName}`);
12280
12361
  } catch (err) {
12281
- const message = getErrorMessage(err, "Failed to leave workspace");
12282
- if (isSingleAdminError(message)) {
12362
+ if (isOnlyWorkspaceAdmin(err)) {
12283
12363
  toast.error("You are the only admin. Promote another admin before leaving.");
12284
12364
  } else {
12285
- toast.error(message);
12365
+ toast.error(errorMessage(err, { method: "POST" }));
12286
12366
  }
12287
12367
  }
12288
12368
  },
@@ -12312,7 +12392,7 @@ function SettingsContentInner({
12312
12392
  setDeletingWorkspace(null);
12313
12393
  await completeWorkspaceRemoval(wsId, `Workspace ${wsName} deleted`);
12314
12394
  } catch (err) {
12315
- toast.error(getErrorMessage(err, "Failed to delete workspace"));
12395
+ toast.error(errorMessage(err, { method: "DELETE" }));
12316
12396
  }
12317
12397
  }
12318
12398
  }