@ai-matrx/sharing 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs ADDED
@@ -0,0 +1,4574 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/react/index.ts
32
+ var react_exports = {};
33
+ __export(react_exports, {
34
+ AccessSummaryPanel: () => AccessSummaryPanel,
35
+ AddEveryoneInOrg: () => AddEveryoneInOrg,
36
+ GuestBadge: () => GuestBadge,
37
+ LANE_CLOSED_SAY: () => LANE_CLOSED_SAY,
38
+ OrgAvailabilityNote: () => OrgAvailabilityNote,
39
+ OutsideSharePanel: () => OutsideSharePanel,
40
+ PermissionBadge: () => PermissionBadge,
41
+ PermissionLevelDescription: () => PermissionLevelDescription,
42
+ PermissionsList: () => PermissionsList,
43
+ PublicAccessTab: () => PublicAccessTab,
44
+ PublicBadge: () => PublicBadge,
45
+ RowControls: () => RowControls,
46
+ SearchEngineIndexedSwitch: () => SearchEngineIndexedSwitch,
47
+ SecureLinkPanel: () => SecureLinkPanel,
48
+ ShareLinkPanel: () => ShareLinkPanel,
49
+ ShareModal: () => ShareModal,
50
+ ShareWithUserTab: () => ShareWithUserTab,
51
+ WhoCanSeeThis: () => WhoCanSeeThis,
52
+ WhoCouldSeeOn: () => WhoCouldSeeOn,
53
+ anyoneReachWords: () => anyoneReachWords,
54
+ configureShareSheetUi: () => configureShareSheetUi,
55
+ resetShareSheetCache: () => resetShareSheetCache,
56
+ shareSheetUi: () => shareSheetUi,
57
+ useAccessSummary: () => useAccessSummary,
58
+ useCanAdmin: () => useCanAdmin,
59
+ useCanEdit: () => useCanEdit,
60
+ useGrantableLevels: () => useGrantableLevels,
61
+ useIsOwner: () => useIsOwner,
62
+ usePermissionCheck: () => usePermissionCheck,
63
+ usePermissions: () => usePermissions,
64
+ useResourcePermissions: () => useResourcePermissions,
65
+ useShare: () => useShare,
66
+ useSharedWithMe: () => useSharedWithMe,
67
+ useSharing: () => useSharing,
68
+ useSharingStatus: () => useSharingStatus
69
+ });
70
+ module.exports = __toCommonJS(react_exports);
71
+
72
+ // src/react/sheet/host.tsx
73
+ var import_react = require("react");
74
+ var import_design_system = require("@ai-matrx/design-system");
75
+ var import_clipboard = require("@ai-matrx/kit/clipboard");
76
+ var import_confirm_opener = require("@ai-matrx/kit/confirm-opener");
77
+ var import_core = require("@ai-matrx/sharing/core");
78
+ var import_core2 = require("@ai-matrx/sharing/core");
79
+ var import_vault = require("@ai-matrx/vault");
80
+ var import_core3 = require("@ai-matrx/sharing/core");
81
+ var import_core4 = require("@ai-matrx/sharing/core");
82
+ var import_jsx_runtime = require("react/jsx-runtime");
83
+ function DefaultCopyButtons(props) {
84
+ const [done, setDone] = (0, import_react.useState)(false);
85
+ const text = () => {
86
+ const h = props.human ?? props.text;
87
+ const v = typeof h === "function" ? h() : h;
88
+ return Array.isArray(v) ? v.join("\n") : String(v ?? "");
89
+ };
90
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
91
+ import_design_system.Button,
92
+ {
93
+ type: "button",
94
+ variant: "ghost",
95
+ size: "sm",
96
+ className: props.className,
97
+ onClick: () => {
98
+ void (0, import_clipboard.copyText)(text()).then(() => {
99
+ setDone(true);
100
+ setTimeout(() => setDone(false), 1500);
101
+ });
102
+ },
103
+ children: done ? "Copied" : props.label ? `Copy ${String(props.label).toLowerCase()}` : "Copy"
104
+ }
105
+ );
106
+ }
107
+ function DefaultErrorMenu(_props) {
108
+ return null;
109
+ }
110
+ function DefaultInfoHint(props) {
111
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { title: String(props.text ?? ""), "aria-label": String(props.label ?? "More info"), className: "cursor-help text-muted-foreground", children: "\u24D8" });
112
+ }
113
+ function DefaultEntityRef(props) {
114
+ const href = typeof props.href === "string" ? props.href : null;
115
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href, className: "underline-offset-2 hover:underline", children: String(props.name ?? "") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: String(props.name ?? "") });
116
+ }
117
+ function DefaultUserAvatar(props) {
118
+ const u = props.user ?? {};
119
+ const name = u.displayName ?? u.display_name ?? u.email ?? "?";
120
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-flex h-7 w-7 items-center justify-center rounded-full bg-muted text-xs font-medium", "aria-label": name, children: name.slice(0, 1).toUpperCase() });
121
+ }
122
+ function DefaultConnectionsNotice(props) {
123
+ if (!props.error && !(Array.isArray(props.partialFailures) && props.partialFailures.length)) return null;
124
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-md border border-destructive/30 bg-destructive/10 p-2 text-xs text-destructive", children: [
125
+ props.error ? `Contacts could not be read: ${String(props.error)}` : `Some contacts could not be read.`,
126
+ " ",
127
+ props.onRetry ? (
128
+ // ui-exception: moved verbatim from the web share sheet; restyling onto design-system Button is its own UI change
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "underline", onClick: () => props.onRetry(), children: "Retry" })
130
+ ) : null
131
+ ] });
132
+ }
133
+ function DefaultUserSearchField(props) {
134
+ const { onValueChange, onUserSelect: _onUserSelect, value, ...rest } = props;
135
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_design_system.Input, { ...rest, value: value ?? "", onChange: (e) => onValueChange?.(e.target.value) });
136
+ }
137
+ var ORGS_EMPTY = [];
138
+ var orgsCache = null;
139
+ var orgListeners = /* @__PURE__ */ new Set();
140
+ function useDefaultOrganizations() {
141
+ const snapshot = (0, import_react.useSyncExternalStore)(
142
+ (cb) => {
143
+ orgListeners.add(cb);
144
+ return () => orgListeners.delete(cb);
145
+ },
146
+ () => orgsCache ?? ORGS_EMPTY,
147
+ () => ORGS_EMPTY
148
+ );
149
+ (0, import_react.useEffect)(() => {
150
+ if (orgsCache) return;
151
+ orgsCache = ORGS_EMPTY;
152
+ void import("@ai-matrx/sharing/core").then(async ({ shareClient }) => {
153
+ const { data, error } = await shareClient().from("organizations").select("id, name");
154
+ if (error) console.error(`[share] Your organizations could not be read: ${error.message}`);
155
+ orgsCache = (Array.isArray(data) ? data : []).map((o) => ({ ...o, id: String(o.id), name: String(o.name ?? "") }));
156
+ orgListeners.forEach((l) => l());
157
+ }).catch((err) => console.error("[share] Your organizations could not be read:", err));
158
+ }, []);
159
+ return snapshot;
160
+ }
161
+ function useDefaultCurrentUserId() {
162
+ const [id, setId] = (0, import_react.useState)(null);
163
+ (0, import_react.useEffect)(() => {
164
+ void import("@ai-matrx/sharing/core").then(async ({ shareClient }) => {
165
+ const { getClaimsUser } = await import("@ai-matrx/sharing/core");
166
+ const { data } = await getClaimsUser(shareClient());
167
+ setId(data.user?.id ?? null);
168
+ });
169
+ }, []);
170
+ return id;
171
+ }
172
+ function useDefaultConnections({ organizationId }) {
173
+ const [state2, setState] = (0, import_react.useState)({ rows: [], loading: Boolean(organizationId), error: null });
174
+ const load = (0, import_react.useCallback)(
175
+ async (fresh = false) => {
176
+ if (!organizationId) return;
177
+ setState((s) => ({ ...s, loading: true }));
178
+ try {
179
+ const rows = await (0, import_core.readOrganizationMemberRows)(organizationId, { fresh });
180
+ setState({
181
+ rows: rows.map((r) => ({
182
+ user_id: r.user_id,
183
+ email: r.user_email ?? null,
184
+ display_name: r.user_display_name ?? null,
185
+ avatar_url: r.user_avatar_url ?? null,
186
+ source: "organization"
187
+ })),
188
+ loading: false,
189
+ error: null
190
+ });
191
+ } catch (err) {
192
+ setState({ rows: [], loading: false, error: err instanceof Error ? err.message : String(err) });
193
+ }
194
+ },
195
+ [organizationId]
196
+ );
197
+ (0, import_react.useEffect)(() => {
198
+ void load();
199
+ }, [load]);
200
+ return (0, import_react.useMemo)(
201
+ () => ({ connections: state2.rows, isLoading: state2.loading, error: state2.error, partialFailures: [], refresh: () => load(true) }),
202
+ [state2, load]
203
+ );
204
+ }
205
+ var DEFAULTS = {
206
+ toast: ({ title, description, variant }) => {
207
+ const text = [title, description].filter((x) => typeof x === "string").join(" \u2014 ");
208
+ if (variant === "destructive") {
209
+ console.error(`[share] ${text}`);
210
+ if (typeof window !== "undefined") window.alert(text);
211
+ } else console.info(`[share] ${text}`);
212
+ },
213
+ useCurrentUserId: useDefaultCurrentUserId,
214
+ useOrganizations: useDefaultOrganizations,
215
+ useUserConnections: useDefaultConnections,
216
+ confirm: (o) => (0, import_confirm_opener.confirm)(o),
217
+ CopyButtons: DefaultCopyButtons,
218
+ csvExportItem: () => null,
219
+ jsonExportItem: () => null,
220
+ usePageCapture: () => void 0,
221
+ dialogCapture: (input) => input,
222
+ PageCaptureButton: () => null,
223
+ ErrorAlchemyMenu: DefaultErrorMenu,
224
+ InfoHint: DefaultInfoHint,
225
+ EntityDoorControls: () => null,
226
+ EntityRef: DefaultEntityRef,
227
+ UserAvatarDisplay: DefaultUserAvatar,
228
+ ConnectionsReadNotice: DefaultConnectionsNotice,
229
+ UserSearchField: DefaultUserSearchField,
230
+ vaultIdentity: null,
231
+ humanLines: (...args) => args.flat().filter(Boolean).map(String).join("\n"),
232
+ usePortalContainer: () => void 0
233
+ };
234
+ var ui = DEFAULTS;
235
+ function configureShareSheetUi(next) {
236
+ ui = { ...ui, ...next };
237
+ }
238
+ function shareSheetUi() {
239
+ return ui;
240
+ }
241
+ function useToast() {
242
+ return { toast: (o) => ui.toast(o) };
243
+ }
244
+ function copyNotify(message, kind) {
245
+ ui.toast({ title: message, ...kind === "error" ? { variant: "destructive" } : {} });
246
+ }
247
+ var confirm = (options) => ui.confirm(options);
248
+ var useCurrentUserId = () => ui.useCurrentUserId();
249
+ var useNavTree = () => ({ orgs: ui.useOrganizations() });
250
+ var useOrganizationsToCreateIn = () => ({ orgs: ui.useOrganizations() });
251
+ var useUserConnections = (options) => ui.useUserConnections(options);
252
+ var usePageCapture = (build, options) => ui.usePageCapture(build, options);
253
+ var dialogCapture = (input) => ui.dialogCapture(input);
254
+ var csvExportItem = (...args) => ui.csvExportItem(...args);
255
+ var jsonExportItem = (...args) => ui.jsonExportItem(...args);
256
+ var CopyButtons = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.CopyButtons, { ...p });
257
+ var PageCaptureButton = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.PageCaptureButton, { ...p });
258
+ var ErrorAlchemyMenu = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.ErrorAlchemyMenu, { ...p });
259
+ var InfoHint = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.InfoHint, { ...p });
260
+ var EntityDoorControls = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.EntityDoorControls, { ...p });
261
+ var EntityRef = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.EntityRef, { ...p });
262
+ var UserAvatarDisplay = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.UserAvatarDisplay, { ...p });
263
+ var ConnectionsReadNotice = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.ConnectionsReadNotice, { ...p });
264
+ var UserSearchField = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ui.UserSearchField, { ...p });
265
+ var defaultIdentity = null;
266
+ var usePortalContainer = () => ui.usePortalContainer();
267
+ var vaultIdentity = () => {
268
+ if (ui.vaultIdentity) return ui.vaultIdentity;
269
+ if (!defaultIdentity) {
270
+ defaultIdentity = (0, import_vault.createSupabaseIdentity)((0, import_core2.shareClient)().auth, {
271
+ redirectTo: () => window.location.href,
272
+ onDifferentPerson: async (message) => {
273
+ window.alert(message);
274
+ }
275
+ });
276
+ }
277
+ return defaultIdentity;
278
+ };
279
+ var state = { sharingStatus: { byKey: {}, authorityByKey: {} } };
280
+ var listeners = /* @__PURE__ */ new Set();
281
+ function reduce(s, a) {
282
+ const { key } = a.payload;
283
+ const byKey = { ...s.byKey };
284
+ const authorityByKey = { ...s.authorityByKey };
285
+ switch (a.type) {
286
+ case "requested":
287
+ byKey[key] = { visibility: byKey[key]?.visibility ?? null, loading: true, error: null };
288
+ break;
289
+ case "loaded":
290
+ byKey[key] = { visibility: a.payload.visibility, loading: false, error: null };
291
+ break;
292
+ case "failed":
293
+ byKey[key] = { visibility: byKey[key]?.visibility ?? null, loading: false, error: a.payload.error };
294
+ break;
295
+ case "authorityRequested":
296
+ authorityByKey[key] = { isOwner: authorityByKey[key]?.isOwner ?? false, loading: true, error: authorityByKey[key]?.error ?? null };
297
+ break;
298
+ case "authorityResolved":
299
+ authorityByKey[key] = { isOwner: a.payload.isOwner, loading: false, error: a.payload.error };
300
+ break;
301
+ }
302
+ return { byKey, authorityByKey };
303
+ }
304
+ var store = {
305
+ getState: () => state,
306
+ dispatch: (a) => {
307
+ state = { sharingStatus: reduce(state.sharingStatus, a) };
308
+ listeners.forEach((l) => l());
309
+ return a;
310
+ }
311
+ };
312
+ var useAppStore = () => store;
313
+ function useAppSelector(select) {
314
+ return (0, import_react.useSyncExternalStore)(
315
+ (cb) => {
316
+ listeners.add(cb);
317
+ return () => listeners.delete(cb);
318
+ },
319
+ () => select(state),
320
+ () => select(state)
321
+ );
322
+ }
323
+ var sharingStatusKey = (resourceType, resourceId) => `${resourceType}:${resourceId}`;
324
+ var sharingStatusRequested = (payload) => ({ type: "requested", payload });
325
+ var sharingStatusLoaded = (payload) => ({ type: "loaded", payload });
326
+ var sharingStatusFailed = (payload) => ({ type: "failed", payload });
327
+ var sharingAuthorityRequested = (payload) => ({ type: "authorityRequested", payload });
328
+ var sharingAuthorityResolved = (payload) => ({ type: "authorityResolved", payload });
329
+ var selectSharingStatus = (s, key) => s.sharingStatus.byKey[key];
330
+ var selectSharingAuthority = (s, key) => s.sharingStatus.authorityByKey[key];
331
+ function resetShareSheetCache() {
332
+ state = { sharingStatus: { byKey: {}, authorityByKey: {} } };
333
+ listeners.forEach((l) => l());
334
+ }
335
+ var storeReads = /* @__PURE__ */ new Map();
336
+ function useStoreRead(key, read, options = {}) {
337
+ const activeKey = options.enabled === false ? null : key;
338
+ const kept = activeKey ? storeReads.get(activeKey) : void 0;
339
+ const [s, setS] = (0, import_react.useState)({
340
+ data: kept?.data,
341
+ loading: Boolean(activeKey) && !kept,
342
+ error: null
343
+ });
344
+ const refresh = (0, import_react.useCallback)(async () => {
345
+ if (!activeKey) return;
346
+ setS((p) => ({ ...p, loading: true }));
347
+ try {
348
+ const data = await read();
349
+ storeReads.set(activeKey, { data, at: Date.now() });
350
+ setS({ data, loading: false, error: null });
351
+ } catch (err) {
352
+ setS((p) => ({ data: p.data, loading: false, error: err instanceof Error ? err.message : String(err) }));
353
+ }
354
+ }, [activeKey]);
355
+ (0, import_react.useEffect)(() => {
356
+ if (!activeKey) return;
357
+ const k = storeReads.get(activeKey);
358
+ const stale = !k || options.staleAfterMs !== void 0 && Date.now() - k.at > options.staleAfterMs;
359
+ if (stale) void refresh();
360
+ else setS({ data: k.data, loading: false, error: null });
361
+ }, [activeKey, refresh]);
362
+ return { data: s.data, isLoading: s.loading, isError: s.error !== null, error: s.error, refresh };
363
+ }
364
+ function useRefreshWhenChanged(token, refresh) {
365
+ const [first, setFirst] = (0, import_react.useState)(true);
366
+ (0, import_react.useEffect)(() => {
367
+ if (first) {
368
+ setFirst(false);
369
+ return;
370
+ }
371
+ void refresh();
372
+ }, [token]);
373
+ }
374
+ var useAppDispatch = () => import_core4.passThroughDispatch;
375
+
376
+ // src/react/sheet/ShareModal.tsx
377
+ var import_core34 = require("@ai-matrx/sharing/core");
378
+ var import_react17 = require("react");
379
+
380
+ // src/react/sheet/ui/dialog.tsx
381
+ var import_design_system2 = require("@ai-matrx/design-system");
382
+
383
+ // src/react/sheet/ShareModal.tsx
384
+ var import_design_system33 = require("@ai-matrx/design-system");
385
+ var import_controls11 = require("@ai-matrx/design-system/controls");
386
+
387
+ // src/react/sheet/ui/share-tabs-trigger.tsx
388
+ var React = __toESM(require("react"), 1);
389
+ var import_controls = require("@ai-matrx/design-system/controls");
390
+ var import_jsx_runtime2 = require("react/jsx-runtime");
391
+ function focusOnClick(onClick) {
392
+ return (event) => {
393
+ onClick?.(event);
394
+ if (event.defaultPrevented) return;
395
+ const trigger = event.currentTarget;
396
+ if (trigger.disabled) return;
397
+ if (document.activeElement !== trigger) trigger.focus();
398
+ };
399
+ }
400
+ var ShareTabsTrigger = React.forwardRef(
401
+ ({ onClick, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls.TabsTrigger, { ...props, ref, onClick: focusOnClick(onClick) })
402
+ );
403
+ ShareTabsTrigger.displayName = "ShareTabsTrigger";
404
+
405
+ // src/react/sheet/ShareModal.tsx
406
+ var import_design_system34 = require("@ai-matrx/design-system");
407
+ var import_lucide_react14 = require("lucide-react");
408
+
409
+ // src/react/permissionHooks.ts
410
+ var import_react2 = require("react");
411
+ var import_core5 = require("@ai-matrx/sharing/core");
412
+ var import_core6 = require("@ai-matrx/sharing/core");
413
+ function usePermissions(resourceType, resourceId, enabled = true) {
414
+ const [permissions, setPermissions] = (0, import_react2.useState)([]);
415
+ const [loading, setLoading] = (0, import_react2.useState)(false);
416
+ const [error, setError] = (0, import_react2.useState)(null);
417
+ const fetchPermissions = (0, import_react2.useCallback)(async () => {
418
+ if (!resourceType || !resourceId || !enabled) return;
419
+ setLoading(true);
420
+ setError(null);
421
+ try {
422
+ const data = await (0, import_core6.listPermissions)(resourceType, resourceId);
423
+ setPermissions(data);
424
+ } catch (err) {
425
+ console.error("Error fetching permissions:", err);
426
+ setError((0, import_core5.extractErrorMessage)(err) || "Failed to fetch permissions");
427
+ } finally {
428
+ setLoading(false);
429
+ }
430
+ }, [resourceType, resourceId, enabled]);
431
+ (0, import_react2.useEffect)(() => {
432
+ if (enabled) {
433
+ fetchPermissions();
434
+ }
435
+ }, [fetchPermissions, enabled]);
436
+ return {
437
+ permissions,
438
+ loading,
439
+ error,
440
+ refresh: fetchPermissions
441
+ };
442
+ }
443
+ function useResourcePermissions(resourceType, resourceId) {
444
+ const [permissions, setPermissions] = (0, import_react2.useState)([]);
445
+ const [loading, setLoading] = (0, import_react2.useState)(true);
446
+ const [error, setError] = (0, import_react2.useState)(null);
447
+ const fetchPermissions = (0, import_react2.useCallback)(async () => {
448
+ if (!resourceType || !resourceId) return;
449
+ setLoading(true);
450
+ setError(null);
451
+ try {
452
+ const data = await (0, import_core6.getResourcePermissions)(resourceType, resourceId);
453
+ setPermissions(data);
454
+ } catch (err) {
455
+ console.error("Error fetching resource permissions:", err);
456
+ setError(
457
+ (0, import_core5.extractErrorMessage)(err) || "Failed to fetch resource permissions"
458
+ );
459
+ } finally {
460
+ setLoading(false);
461
+ }
462
+ }, [resourceType, resourceId]);
463
+ (0, import_react2.useEffect)(() => {
464
+ fetchPermissions();
465
+ }, [fetchPermissions]);
466
+ return {
467
+ permissions,
468
+ loading,
469
+ error,
470
+ refresh: fetchPermissions
471
+ };
472
+ }
473
+ function usePermissionCheck(options) {
474
+ const [result, setResult] = (0, import_react2.useState)({
475
+ hasAccess: false,
476
+ isOwner: false
477
+ });
478
+ const [loading, setLoading] = (0, import_react2.useState)(true);
479
+ (0, import_react2.useEffect)(() => {
480
+ const check = async () => {
481
+ setLoading(true);
482
+ const checkResult = await (0, import_core6.checkPermission)(options);
483
+ setResult(checkResult);
484
+ setLoading(false);
485
+ };
486
+ if (options.resourceType && options.resourceId) {
487
+ check();
488
+ }
489
+ }, [options.resourceType, options.resourceId, options.requiredLevel]);
490
+ return {
491
+ ...result,
492
+ loading
493
+ };
494
+ }
495
+ function useCanEdit(resourceType, resourceId) {
496
+ const { hasAccess, loading } = usePermissionCheck({
497
+ resourceType,
498
+ resourceId,
499
+ requiredLevel: "editor"
500
+ });
501
+ return {
502
+ canEdit: hasAccess,
503
+ loading
504
+ };
505
+ }
506
+ function useCanAdmin(resourceType, resourceId) {
507
+ const { hasAccess, loading } = usePermissionCheck({
508
+ resourceType,
509
+ resourceId,
510
+ requiredLevel: "admin"
511
+ });
512
+ return {
513
+ canAdmin: hasAccess,
514
+ loading
515
+ };
516
+ }
517
+ function useIsOwner(resourceType, resourceId) {
518
+ const store2 = useAppStore();
519
+ const key = sharingStatusKey(resourceType, resourceId);
520
+ const entry = useAppSelector((state2) => selectSharingAuthority(state2, key));
521
+ const asked = Boolean(resourceType && resourceId);
522
+ const known = entry !== void 0;
523
+ (0, import_react2.useEffect)(() => {
524
+ if (known || !asked) return;
525
+ if (selectSharingAuthority(store2.getState(), key)) return;
526
+ store2.dispatch(sharingAuthorityRequested({ key }));
527
+ void (0, import_core6.resolveSharingAuthority)(resourceType, resourceId).then((result) => {
528
+ store2.dispatch(sharingAuthorityResolved({ key, isOwner: result.isOwner, error: result.error }));
529
+ });
530
+ }, [known, asked, key]);
531
+ if (!asked) return { isOwner: false, loading: false, error: null };
532
+ return {
533
+ isOwner: entry?.isOwner ?? false,
534
+ loading: entry ? entry.loading : true,
535
+ error: entry?.error ?? null
536
+ };
537
+ }
538
+ function useSharing(resourceType, resourceId, enabled = true, resourceName, organizationId) {
539
+ const [loading, setLoading] = (0, import_react2.useState)(false);
540
+ const [error, setError] = (0, import_react2.useState)(null);
541
+ const [rowState, setRowState] = (0, import_react2.useState)({
542
+ isPublic: false
543
+ });
544
+ const { permissions, refresh: refreshPermissions } = usePermissions(
545
+ resourceType,
546
+ resourceId,
547
+ enabled
548
+ );
549
+ const refreshRowState = (0, import_react2.useCallback)(async () => {
550
+ if (!resourceType || !resourceId || !enabled) return;
551
+ try {
552
+ const v = await (0, import_core6.getResourceVisibility)(resourceType, resourceId);
553
+ setRowState(v);
554
+ } catch (err) {
555
+ console.error("Error fetching the record's row controls:", err);
556
+ setError((0, import_core5.extractErrorMessage)(err));
557
+ }
558
+ }, [resourceType, resourceId, enabled]);
559
+ const refresh = (0, import_react2.useCallback)(async () => {
560
+ await Promise.all([refreshPermissions(), refreshRowState()]);
561
+ }, [refreshPermissions, refreshRowState]);
562
+ (0, import_react2.useEffect)(() => {
563
+ if (enabled) {
564
+ void refreshRowState();
565
+ }
566
+ }, [enabled, refreshRowState]);
567
+ const handleShareWithUser = (0, import_react2.useCallback)(
568
+ async (userId, permissionLevel) => {
569
+ setLoading(true);
570
+ setError(null);
571
+ try {
572
+ const result = await (0, import_core6.shareWithUser)({
573
+ resourceType,
574
+ resourceId,
575
+ userId,
576
+ permissionLevel,
577
+ resourceName,
578
+ organizationId: organizationId ?? null
579
+ });
580
+ if (!result.success) {
581
+ setError(result.error || "Failed to share");
582
+ return result;
583
+ }
584
+ await refresh();
585
+ return result;
586
+ } catch (err) {
587
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || "Failed to share with user";
588
+ setError(errorMessage);
589
+ return { success: false, error: errorMessage };
590
+ } finally {
591
+ setLoading(false);
592
+ }
593
+ },
594
+ [resourceType, resourceId, resourceName, organizationId, refresh]
595
+ );
596
+ const handleMakePublic = (0, import_react2.useCallback)(
597
+ async (permissionLevel = "viewer") => {
598
+ setLoading(true);
599
+ setError(null);
600
+ try {
601
+ const result = await (0, import_core6.makePublic)({
602
+ resourceType,
603
+ resourceId,
604
+ permissionLevel
605
+ });
606
+ if (!result.success) {
607
+ setError(result.error || "Failed to make public");
608
+ return result;
609
+ }
610
+ await refresh();
611
+ return result;
612
+ } catch (err) {
613
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || "Failed to make public";
614
+ setError(errorMessage);
615
+ return { success: false, error: errorMessage };
616
+ } finally {
617
+ setLoading(false);
618
+ }
619
+ },
620
+ [resourceType, resourceId, refresh]
621
+ );
622
+ const handleRevokeAccess = (0, import_react2.useCallback)(
623
+ async (options) => {
624
+ setLoading(true);
625
+ setError(null);
626
+ try {
627
+ const result = await (0, import_core6.revokeAccess)({
628
+ resourceType,
629
+ resourceId,
630
+ ...options
631
+ });
632
+ if (!result.success) {
633
+ setError(result.error || "Failed to revoke access");
634
+ return result;
635
+ }
636
+ await refresh();
637
+ return result;
638
+ } catch (err) {
639
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || "Failed to revoke access";
640
+ setError(errorMessage);
641
+ return { success: false, error: errorMessage };
642
+ } finally {
643
+ setLoading(false);
644
+ }
645
+ },
646
+ [resourceType, resourceId, refresh]
647
+ );
648
+ const handleRevokeOrgAccess = (0, import_react2.useCallback)(
649
+ async (organizationId2) => {
650
+ setLoading(true);
651
+ setError(null);
652
+ try {
653
+ const result = await (0, import_core6.revokeOrgAccess)(
654
+ resourceType,
655
+ resourceId,
656
+ organizationId2
657
+ );
658
+ if (!result.success) {
659
+ setError(result.error || "Failed to revoke org access");
660
+ return result;
661
+ }
662
+ await refresh();
663
+ return result;
664
+ } catch (err) {
665
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || "Failed to revoke org access";
666
+ setError(errorMessage);
667
+ return { success: false, error: errorMessage };
668
+ } finally {
669
+ setLoading(false);
670
+ }
671
+ },
672
+ [resourceType, resourceId, refresh]
673
+ );
674
+ const handleUpdateLevel = (0, import_react2.useCallback)(
675
+ async (options, newLevel) => {
676
+ setLoading(true);
677
+ setError(null);
678
+ try {
679
+ const result = await (0, import_core6.updatePermissionLevel)({
680
+ resourceType,
681
+ resourceId,
682
+ ...options,
683
+ newLevel
684
+ });
685
+ if (!result.success) {
686
+ setError(result.error || "Failed to update permission");
687
+ return result;
688
+ }
689
+ await refresh();
690
+ return result;
691
+ } catch (err) {
692
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || "Failed to update permission";
693
+ setError(errorMessage);
694
+ return { success: false, error: errorMessage };
695
+ } finally {
696
+ setLoading(false);
697
+ }
698
+ },
699
+ [resourceType, resourceId, refresh]
700
+ );
701
+ const handleSetShownTo = (0, import_react2.useCallback)(
702
+ async (next) => {
703
+ setLoading(true);
704
+ setError(null);
705
+ try {
706
+ const result = await (0, import_core6.setResourceShownTo)(resourceType, resourceId, next);
707
+ if (!result.success) {
708
+ setError(result.error || 'Could not change "Shown to"');
709
+ return result;
710
+ }
711
+ await refresh();
712
+ return result;
713
+ } catch (err) {
714
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || 'Could not change "Shown to"';
715
+ setError(errorMessage);
716
+ return { success: false, error: errorMessage };
717
+ } finally {
718
+ setLoading(false);
719
+ }
720
+ },
721
+ [resourceType, resourceId, refresh]
722
+ );
723
+ const whoCanSee = rowState.whoCanSee ? {
724
+ ...rowState.whoCanSee,
725
+ organizationId: rowState.whoCanSee.organizationId ?? organizationId ?? null
726
+ } : null;
727
+ const handleSetWhoCanSee = (0, import_react2.useCallback)(
728
+ async (choice) => {
729
+ if (!whoCanSee) {
730
+ return { success: false, error: "This item has no lane to choose." };
731
+ }
732
+ if (!whoCanSee.organizationId) {
733
+ return {
734
+ success: false,
735
+ error: "This item's organization could not be read, so its lane cannot be changed."
736
+ };
737
+ }
738
+ setLoading(true);
739
+ setError(null);
740
+ try {
741
+ const result = await (0, import_core6.setStoreLane)(whoCanSee.organizationId, resourceId, choice);
742
+ if (!result.success) {
743
+ setError(result.error || "Failed to change who can see this");
744
+ return result;
745
+ }
746
+ await refresh();
747
+ return result;
748
+ } catch (err) {
749
+ const errorMessage = (0, import_core5.extractErrorMessage)(err) || "Failed to change who can see this";
750
+ setError(errorMessage);
751
+ return { success: false, error: errorMessage };
752
+ } finally {
753
+ setLoading(false);
754
+ }
755
+ },
756
+ [whoCanSee, resourceId, refresh]
757
+ );
758
+ return {
759
+ permissions,
760
+ /** Published to the web (or the card's own publish). */
761
+ isPublic: rowState.isPublic,
762
+ /**
763
+ * "Shown to" — undefined when the type does not carry it (Private, Confidential, child, or a
764
+ * table without the column); null when the type's default applies.
765
+ */
766
+ shownTo: rowState.shownTo,
767
+ /** A child record (a file attached to a chat): no row control of its own. */
768
+ childRecord: rowState.childRecord === true,
769
+ /** Membership alone reaches it — said under Current Access. The record store's door names it. */
770
+ organizationDefault: rowState.organizationDefault ?? null,
771
+ whoCanSee,
772
+ /** The thing's own organization, read off its row; null when unknown. */
773
+ homeOrganizationId: rowState.homeOrganizationId ?? null,
774
+ setWhoCanSee: handleSetWhoCanSee,
775
+ loading,
776
+ error,
777
+ shareWithUser: handleShareWithUser,
778
+ makePublic: handleMakePublic,
779
+ setShownTo: handleSetShownTo,
780
+ revokeAccess: handleRevokeAccess,
781
+ revokeOrgAccess: handleRevokeOrgAccess,
782
+ updateLevel: handleUpdateLevel,
783
+ refresh,
784
+ refreshRowState
785
+ };
786
+ }
787
+ function useSharedWithMe(resourceType) {
788
+ const [permissions, setPermissions] = (0, import_react2.useState)([]);
789
+ const [loading, setLoading] = (0, import_react2.useState)(true);
790
+ const [error, setError] = (0, import_react2.useState)(null);
791
+ const fetchShared = (0, import_react2.useCallback)(async () => {
792
+ setLoading(true);
793
+ setError(null);
794
+ try {
795
+ const data = await (0, import_core6.getSharedWithMe)(resourceType);
796
+ setPermissions(data);
797
+ } catch (err) {
798
+ console.error("Error fetching shared resources:", err);
799
+ setError((0, import_core5.extractErrorMessage)(err) || "Failed to fetch shared resources");
800
+ } finally {
801
+ setLoading(false);
802
+ }
803
+ }, [resourceType]);
804
+ (0, import_react2.useEffect)(() => {
805
+ fetchShared();
806
+ }, [fetchShared]);
807
+ return {
808
+ permissions,
809
+ loading,
810
+ error,
811
+ refresh: fetchShared
812
+ };
813
+ }
814
+ function useSharingStatus(resourceType, resourceId, enabled = true) {
815
+ const store2 = useAppStore();
816
+ const key = sharingStatusKey(resourceType, resourceId);
817
+ const entry = useAppSelector((state2) => selectSharingStatus(state2, key));
818
+ const refresh = async () => {
819
+ if (!resourceType || !resourceId || !enabled) return;
820
+ store2.dispatch(sharingStatusRequested({ key }));
821
+ try {
822
+ const v = await (0, import_core6.getResourceVisibility)(resourceType, resourceId);
823
+ store2.dispatch(sharingStatusLoaded({ key, visibility: v }));
824
+ } catch (err) {
825
+ console.error("Error fetching sharing status:", err);
826
+ store2.dispatch(sharingStatusFailed({ key, error: (0, import_core5.extractErrorMessage)(err) }));
827
+ }
828
+ };
829
+ const known = entry !== void 0;
830
+ (0, import_react2.useEffect)(() => {
831
+ if (known || !resourceType || !resourceId || !enabled) return;
832
+ if (selectSharingStatus(store2.getState(), key)) return;
833
+ void refresh();
834
+ }, [known, key, enabled]);
835
+ return {
836
+ isPublic: entry?.visibility?.isPublic ?? false,
837
+ loading: entry ? entry.loading : enabled,
838
+ error: entry?.error ?? null,
839
+ refresh
840
+ };
841
+ }
842
+
843
+ // src/react/sheet/useGrantableLevels.ts
844
+ var import_react3 = require("react");
845
+ var import_core7 = require("@ai-matrx/sharing/core");
846
+ var NOTHING = { loading: true, error: null, levels: {}, outside: null, maySharePeople: false };
847
+ function useGrantableLevels(resourceType, resourceId, enabled, refreshKey) {
848
+ const [state2, setState] = (0, import_react3.useState)(NOTHING);
849
+ (0, import_react3.useEffect)(() => {
850
+ if (!enabled || !resourceType || !resourceId) return;
851
+ let cancelled = false;
852
+ const supabase = (0, import_core7.createClient)();
853
+ const iam = supabase.schema("iam");
854
+ void iam.rpc("my_grantable_levels", { p_resource_type: resourceType, p_resource_id: resourceId }).then(({ data, error }) => {
855
+ if (cancelled) return;
856
+ if (error) {
857
+ setState({ ...NOTHING, loading: false, error: error.message });
858
+ return;
859
+ }
860
+ const a = data ?? {};
861
+ const levels = a.levels ?? {};
862
+ setState({
863
+ loading: false,
864
+ error: null,
865
+ levels,
866
+ outside: a.outside ?? null,
867
+ maySharePeople: Object.values(levels).some((why) => why === null)
868
+ });
869
+ });
870
+ return () => {
871
+ cancelled = true;
872
+ };
873
+ }, [enabled, resourceType, resourceId, refreshKey]);
874
+ return state2;
875
+ }
876
+
877
+ // src/react/sheet/ShareModal.tsx
878
+ var import_core35 = require("@ai-matrx/sharing/core");
879
+ var import_design_system35 = require("@ai-matrx/design-system");
880
+ var import_lucide_react15 = require("lucide-react");
881
+
882
+ // src/react/sheet/PermissionsList.tsx
883
+ var import_react4 = require("react");
884
+ var import_design_system5 = require("@ai-matrx/design-system");
885
+ var import_design_system6 = require("@ai-matrx/design-system");
886
+ var import_cn = require("@ai-matrx/design-system/cn");
887
+ var import_design_system7 = require("@ai-matrx/design-system");
888
+ var import_controls2 = require("@ai-matrx/design-system/controls");
889
+ var import_lucide_react2 = require("lucide-react");
890
+
891
+ // src/react/sheet/PermissionBadge.tsx
892
+ var import_design_system3 = require("@ai-matrx/design-system");
893
+ var import_lucide_react = require("lucide-react");
894
+ var import_jsx_runtime3 = require("react/jsx-runtime");
895
+ function PermissionBadge({
896
+ level,
897
+ variant = "default",
898
+ showIcon = false
899
+ }) {
900
+ const config = {
901
+ owner: {
902
+ label: "Owner",
903
+ className: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 border-yellow-300",
904
+ icon: import_lucide_react.Crown
905
+ },
906
+ admin: {
907
+ label: "Full access",
908
+ className: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 border-red-300",
909
+ icon: import_lucide_react.Shield
910
+ },
911
+ commenter: {
912
+ label: "Comment",
913
+ className: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200 border-purple-300",
914
+ icon: import_lucide_react.MessageSquare
915
+ },
916
+ editor: {
917
+ label: "Edit",
918
+ className: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 border-orange-300",
919
+ icon: import_lucide_react.Users
920
+ },
921
+ viewer: {
922
+ label: "View",
923
+ className: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 border-blue-300",
924
+ icon: import_lucide_react.Eye
925
+ }
926
+ };
927
+ const shown = level === "edit_content" ? "editor" : level;
928
+ const { label, className, icon: Icon } = config[shown];
929
+ if (variant === "compact") {
930
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system3.Badge, { className: `${className} text-xs px-2 py-0.5`, children: [
931
+ showIcon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "w-3 h-3 mr-1" }),
932
+ label
933
+ ] });
934
+ }
935
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system3.Badge, { className: `${className} px-3 py-1`, children: [
936
+ showIcon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "w-3.5 h-3.5 mr-1.5" }),
937
+ label
938
+ ] });
939
+ }
940
+ function PublicBadge({
941
+ variant = "default"
942
+ }) {
943
+ const className = "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 border-green-300";
944
+ if (variant === "compact") {
945
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system3.Badge, { className: `${className} text-xs px-2 py-0.5`, children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Globe, { className: "w-3 h-3 mr-1" }),
947
+ "Public"
948
+ ] });
949
+ }
950
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system3.Badge, { className: `${className} px-3 py-1`, children: [
951
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Globe, { className: "w-3.5 h-3.5 mr-1.5" }),
952
+ "Public"
953
+ ] });
954
+ }
955
+ function PermissionLevelDescription({
956
+ level
957
+ }) {
958
+ const descriptions = {
959
+ viewer: "Can view",
960
+ commenter: "Can view and comment",
961
+ editor: "Can view and edit values and content",
962
+ admin: "Full access: also structure and sharing"
963
+ };
964
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-muted-foreground", children: descriptions[level] });
965
+ }
966
+ function GuestBadge() {
967
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system3.Badge, { variant: "outline", className: "shrink-0 px-1.5 py-0 text-[10px] leading-4 text-muted-foreground", "data-guest-marker": "", children: "Guest" });
968
+ }
969
+
970
+ // src/react/sheet/PermissionsList.tsx
971
+ var import_core8 = require("@ai-matrx/sharing/core");
972
+ var import_core9 = require("@ai-matrx/sharing/core");
973
+
974
+ // src/react/sheet/ui/alert-dialog.tsx
975
+ var import_design_system4 = require("@ai-matrx/design-system");
976
+
977
+ // src/react/sheet/PermissionsList.tsx
978
+ var import_jsx_runtime4 = (
979
+ // ui-exception: moved verbatim from the web share sheet; restyling onto design-system Button is its own UI change
980
+ require("react/jsx-runtime")
981
+ );
982
+ var GRANT_PREVIEW = 12;
983
+ var EMPTY_STATE_MIN_HEIGHT = "min-h-[11rem]";
984
+ function pendingSentence({
985
+ count,
986
+ one,
987
+ many
988
+ }) {
989
+ return count === 1 ? `One ${one} is invited below and has not joined yet \u2014 they get access the moment they follow their link.` : `${count} ${many} are invited below and have not joined yet \u2014 each gets access the moment they follow their link.`;
990
+ }
991
+ function ShowAllToggle({
992
+ total,
993
+ preview,
994
+ showingAll,
995
+ onToggle
996
+ }) {
997
+ if (total <= preview) return null;
998
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
999
+ "button",
1000
+ {
1001
+ type: "button",
1002
+ className: "text-[11px] text-muted-foreground underline-offset-2 hover:text-foreground ",
1003
+ onClick: onToggle,
1004
+ children: showingAll ? `top ${preview}` : `all ${total.toLocaleString()}`
1005
+ }
1006
+ );
1007
+ }
1008
+ function PermissionsList({
1009
+ permissions,
1010
+ isOwner,
1011
+ onUpdateLevel,
1012
+ onRevoke,
1013
+ loading = false,
1014
+ copy,
1015
+ listLabel = "Current access",
1016
+ alsoPending
1017
+ }) {
1018
+ const [updatingId, setUpdatingId] = (0, import_react4.useState)(null);
1019
+ const [revokingId, setRevokingId] = (0, import_react4.useState)(null);
1020
+ const [showAll, setShowAll] = (0, import_react4.useState)(false);
1021
+ const [confirmRevoke, setConfirmRevoke] = (0, import_react4.useState)({ open: false, permission: null });
1022
+ const context = copy ?? {
1023
+ resourceType: permissions[0]?.resourceType ?? "unknown",
1024
+ resourceId: permissions[0]?.resourceId ?? "unknown",
1025
+ surface: listLabel,
1026
+ kpis: (0, import_core8.accessKpis)({
1027
+ permissions,
1028
+ isPublic: permissions.some((p) => p.isPublic),
1029
+ // What the list actually renders — owner controls are on screen or not.
1030
+ viewerIsOwner: isOwner
1031
+ })
1032
+ };
1033
+ const location = (0, import_core8.sharingLocation)(context.surface);
1034
+ const resourceAttributes = {
1035
+ resource_type: context.resourceType,
1036
+ resource_id: context.resourceId,
1037
+ resource_name: context.resourceName ?? null
1038
+ };
1039
+ if (loading && permissions.length === 0) {
1040
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1041
+ "div",
1042
+ {
1043
+ className: `flex items-center justify-center ${EMPTY_STATE_MIN_HEIGHT}`,
1044
+ "aria-busy": "true",
1045
+ "data-grants-reserved": "",
1046
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Loader2, { className: "w-6 h-6 animate-spin text-muted-foreground" })
1047
+ }
1048
+ );
1049
+ }
1050
+ if (permissions.length === 0) {
1051
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1052
+ "div",
1053
+ {
1054
+ className: `group flex flex-col items-center justify-center text-center py-6 text-muted-foreground ${EMPTY_STATE_MIN_HEIGHT}`,
1055
+ "data-grants-empty": "",
1056
+ children: [
1057
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Lock, { className: "w-10 h-10 mb-1.5 opacity-20" }),
1058
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm", children: alsoPending && alsoPending.count > 0 ? import_core8.NO_GRANTS_YET_HEADLINE : import_core8.NO_GRANTS_HEADLINE }),
1059
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs mt-0.5", children: import_core8.NO_GRANTS_DETAIL }),
1060
+ alsoPending && alsoPending.count > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs mt-0.5", "data-testid": "grants-also-pending", children: pendingSentence(alsoPending) }) : null,
1061
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-1.5 flex justify-center opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1062
+ CopyButtons,
1063
+ {
1064
+ size: "xs",
1065
+ label: `${listLabel} (no grants)`,
1066
+ human: () => (0, import_core8.humanGrantList)([], { surface: context.surface }),
1067
+ agent: () => ({
1068
+ kind: "access-grants",
1069
+ location,
1070
+ description: `The "${listLabel}" grant list, which is empty: ${import_core8.NO_GRANTS_HEADLINE} \u2014 ${import_core8.NO_GRANTS_DETAIL}.`,
1071
+ data: {
1072
+ list: listLabel,
1073
+ rendered_empty_state: {
1074
+ headline: import_core8.NO_GRANTS_HEADLINE,
1075
+ detail: import_core8.NO_GRANTS_DETAIL
1076
+ },
1077
+ grants: [],
1078
+ scope_note: import_core8.GRANT_LIST_SCOPE_NOTE,
1079
+ kpis: context.kpis
1080
+ },
1081
+ summary: (0, import_core8.humanGrantList)([], { surface: context.surface }),
1082
+ attributes: {
1083
+ ...context.kpis,
1084
+ ...resourceAttributes,
1085
+ list: listLabel,
1086
+ count: 0
1087
+ }
1088
+ })
1089
+ }
1090
+ ) })
1091
+ ]
1092
+ }
1093
+ );
1094
+ }
1095
+ const handleUpdateLevel = async (permission, newLevel) => {
1096
+ if (newLevel === permission.permissionLevel) return;
1097
+ setUpdatingId(permission.id);
1098
+ try {
1099
+ await onUpdateLevel(
1100
+ {
1101
+ userId: permission.grantedToUserId || void 0,
1102
+ organizationId: permission.grantedToOrganizationId || void 0,
1103
+ isPublic: permission.isPublic || void 0
1104
+ },
1105
+ newLevel
1106
+ );
1107
+ } finally {
1108
+ setUpdatingId(null);
1109
+ }
1110
+ };
1111
+ const handleRevoke = async (permission) => {
1112
+ setRevokingId(permission.id);
1113
+ try {
1114
+ await onRevoke({
1115
+ userId: permission.grantedToUserId || void 0,
1116
+ organizationId: permission.grantedToOrganizationId || void 0,
1117
+ isPublic: permission.isPublic || void 0
1118
+ });
1119
+ } finally {
1120
+ setRevokingId(null);
1121
+ setConfirmRevoke({ open: false, permission: null });
1122
+ }
1123
+ };
1124
+ const getPermissionLabel = import_core8.granteeLabel;
1125
+ const getPermissionSecondaryLabel = import_core8.granteeSecondaryLabel;
1126
+ const getPermissionIcon = (permission) => {
1127
+ if (permission.isPublic) return import_lucide_react2.Globe;
1128
+ if (permission.grantedToOrganization) return import_lucide_react2.Building2;
1129
+ return import_lucide_react2.Mail;
1130
+ };
1131
+ const visiblePermissions = showAll ? permissions : permissions.slice(0, GRANT_PREVIEW);
1132
+ const listHuman = () => (0, import_core8.humanGrantList)(permissions, {
1133
+ surface: context.surface,
1134
+ shown: visiblePermissions.length
1135
+ });
1136
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1137
+ "div",
1138
+ {
1139
+ className: (0, import_cn.cn)("space-y-1 transition-opacity", loading && "opacity-60"),
1140
+ "aria-busy": loading,
1141
+ "data-grants-busy": loading ? "true" : "false",
1142
+ children: [
1143
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center justify-end gap-1", children: [
1144
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1145
+ ShowAllToggle,
1146
+ {
1147
+ total: permissions.length,
1148
+ preview: GRANT_PREVIEW,
1149
+ showingAll: showAll,
1150
+ onToggle: () => setShowAll((current) => !current)
1151
+ }
1152
+ ),
1153
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1154
+ CopyButtons,
1155
+ {
1156
+ size: "xs",
1157
+ label: `${listLabel} (${permissions.length} grant${permissions.length === 1 ? "" : "s"})`,
1158
+ human: listHuman,
1159
+ json: () => permissions,
1160
+ agent: () => ({
1161
+ kind: "access-grants",
1162
+ location,
1163
+ description: `All ${permissions.length} direct grant(s) in the "${listLabel}" list. ${import_core8.GRANT_LIST_SCOPE_NOTE}`,
1164
+ data: {
1165
+ list: listLabel,
1166
+ grants: permissions.map((permission) => ({
1167
+ grantee: (0, import_core8.granteeLabel)(permission),
1168
+ grantee_email: (0, import_core8.granteeSecondaryLabel)(permission),
1169
+ grantee_type: (0, import_core8.granteeKind)(permission),
1170
+ level_shown: (0, import_core8.grantLevelLabel)(permission),
1171
+ can_manage_here: isOwner,
1172
+ raw: permission
1173
+ })),
1174
+ scope_note: import_core8.GRANT_LIST_SCOPE_NOTE,
1175
+ kpis: context.kpis,
1176
+ rows_on_screen: visiblePermissions.length,
1177
+ rows_total: permissions.length
1178
+ },
1179
+ summary: listHuman(),
1180
+ attributes: {
1181
+ ...context.kpis,
1182
+ ...resourceAttributes,
1183
+ list: listLabel,
1184
+ count: permissions.length,
1185
+ shown: visiblePermissions.length
1186
+ }
1187
+ }),
1188
+ export: {
1189
+ items: [
1190
+ jsonExportItem(() => permissions, "JSON (all grants)"),
1191
+ csvExportItem(
1192
+ () => (0, import_core8.grantCsvRows)(permissions),
1193
+ "CSV (all grants)"
1194
+ )
1195
+ ]
1196
+ }
1197
+ }
1198
+ )
1199
+ ] }),
1200
+ visiblePermissions.map((permission) => {
1201
+ const Icon = getPermissionIcon(permission);
1202
+ const isUpdating = updatingId === permission.id;
1203
+ const isRevoking = revokingId === permission.id;
1204
+ const secondaryLabel = getPermissionSecondaryLabel(permission);
1205
+ return (
1206
+ // `group` is what reveals the door controls below on hover — without
1207
+ // it EntityDoorControls renders at opacity-0 and the door is
1208
+ // invisible, which is the failure this campaign has hit four times.
1209
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system6.Card, { size: "sm", className: "group px-2 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1210
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
1211
+ permission.grantedToUser && !permission.isPublic ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1212
+ UserAvatarDisplay,
1213
+ {
1214
+ user: permission.grantedToUser,
1215
+ size: "xs",
1216
+ className: "flex-shrink-0"
1217
+ }
1218
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center justify-center w-6 h-6 rounded-full bg-primary/10 flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { className: "w-3 h-3 text-primary" }) }),
1219
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 min-w-0 flex items-center gap-1.5", children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm font-semibold truncate leading-none", children: getPermissionLabel(permission) }),
1221
+ permission.grantedToUser?.isGuest && !permission.isPublic ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GuestBadge, {}) : null,
1222
+ permission.grantedToOrganization && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1223
+ EntityDoorControls,
1224
+ {
1225
+ token: "organization",
1226
+ id: permission.grantedToOrganization.id,
1227
+ name: permission.grantedToOrganization.name,
1228
+ className: "shrink-0"
1229
+ }
1230
+ ),
1231
+ secondaryLabel && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-[11px] text-muted-foreground truncate leading-none", children: secondaryLabel })
1232
+ ] })
1233
+ ] }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
1235
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1236
+ CopyButtons,
1237
+ {
1238
+ size: "xs",
1239
+ label: `Grant \u2014 ${(0, import_core8.granteeLabel)(permission)}`,
1240
+ human: () => (0, import_core8.humanGrantRow)(permission),
1241
+ json: () => permission,
1242
+ agent: () => ({
1243
+ kind: "access-grant",
1244
+ location,
1245
+ description: `One direct access grant on this resource: ${(0, import_core8.granteeLabel)(
1246
+ permission
1247
+ )} \xB7 ${(0, import_core8.grantLevelLabel)(permission)}.`,
1248
+ data: {
1249
+ grantee: (0, import_core8.granteeLabel)(permission),
1250
+ grantee_email: (0, import_core8.granteeSecondaryLabel)(permission),
1251
+ grantee_type: (0, import_core8.granteeKind)(permission),
1252
+ level_shown: (0, import_core8.grantLevelLabel)(permission),
1253
+ can_manage_here: isOwner,
1254
+ raw: permission,
1255
+ scope_note: import_core8.GRANT_LIST_SCOPE_NOTE,
1256
+ kpis: context.kpis
1257
+ },
1258
+ summary: (0, import_core8.humanGrantRow)(permission),
1259
+ attributes: {
1260
+ ...context.kpis,
1261
+ ...resourceAttributes,
1262
+ list: listLabel,
1263
+ grant_id: permission.id,
1264
+ grantee_type: (0, import_core8.granteeKind)(permission),
1265
+ level: permission.permissionLevel
1266
+ }
1267
+ })
1268
+ }
1269
+ ) }),
1270
+ isOwner ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1271
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1272
+ import_design_system7.Select,
1273
+ {
1274
+ value: permission.permissionLevel,
1275
+ onValueChange: (value) => handleUpdateLevel(permission, value),
1276
+ disabled: isUpdating || isRevoking,
1277
+ children: [
1278
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_controls2.SelectTrigger, { className: "w-[140px]", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system7.SelectValue, {}) }),
1279
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system7.SelectContent, { children: [
1280
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system7.SelectItem, { value: "viewer", title: import_core9.PERMISSION_LEVEL_HINTS.viewer, children: import_core9.PERMISSION_LEVEL_SHORT_LABELS.viewer }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system7.SelectItem, { value: "commenter", title: import_core9.PERMISSION_LEVEL_HINTS.commenter, children: import_core9.PERMISSION_LEVEL_SHORT_LABELS.commenter }),
1282
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system7.SelectItem, { value: "editor", title: import_core9.PERMISSION_LEVEL_HINTS.editor, children: import_core9.PERMISSION_LEVEL_SHORT_LABELS.editor }),
1283
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system7.SelectItem, { value: "admin", title: import_core9.PERMISSION_LEVEL_HINTS.admin, children: import_core9.PERMISSION_LEVEL_SHORT_LABELS.admin })
1284
+ ] })
1285
+ ]
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1289
+ import_design_system5.Button,
1290
+ {
1291
+ variant: "ghost",
1292
+ size: "icon",
1293
+ className: "h-7 w-7",
1294
+ "aria-label": `Revoke access for ${getPermissionLabel(permission)}`,
1295
+ title: `Revoke access for ${getPermissionLabel(permission)}`,
1296
+ onClick: () => setConfirmRevoke({ open: true, permission }),
1297
+ disabled: isUpdating || isRevoking,
1298
+ children: isRevoking ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Loader2, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.X, { className: "w-3 h-3" })
1299
+ }
1300
+ )
1301
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: permission.isPublic ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PublicBadge, { variant: "compact" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1302
+ PermissionBadge,
1303
+ {
1304
+ level: permission.permissionLevel,
1305
+ variant: "compact"
1306
+ }
1307
+ ) })
1308
+ ] })
1309
+ ] }) }, permission.id)
1310
+ );
1311
+ }),
1312
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1313
+ import_design_system4.AlertDialog,
1314
+ {
1315
+ open: confirmRevoke.open,
1316
+ onOpenChange: (open) => setConfirmRevoke({ open, permission: confirmRevoke.permission }),
1317
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.AlertDialogContent, { children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.AlertDialogHeader, { children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.AlertDialogTitle, { children: "Revoke Access?" }),
1320
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.AlertDialogDescription, { children: [
1321
+ "Are you sure you want to revoke access for",
1322
+ " ",
1323
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: confirmRevoke.permission ? getPermissionLabel(confirmRevoke.permission) : "" }),
1324
+ "? They will no longer be able to access this resource."
1325
+ ] })
1326
+ ] }),
1327
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system4.AlertDialogFooter, { children: [
1328
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.AlertDialogCancel, { children: "Cancel" }),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1330
+ import_design_system4.AlertDialogAction,
1331
+ {
1332
+ onClick: () => {
1333
+ if (confirmRevoke.permission) {
1334
+ handleRevoke(confirmRevoke.permission);
1335
+ }
1336
+ },
1337
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
1338
+ children: "Revoke Access"
1339
+ }
1340
+ )
1341
+ ] })
1342
+ ] })
1343
+ }
1344
+ )
1345
+ ]
1346
+ }
1347
+ );
1348
+ }
1349
+
1350
+ // src/react/sheet/AccessSummaryPanel.tsx
1351
+ var import_react5 = require("react");
1352
+ var import_lucide_react3 = require("lucide-react");
1353
+
1354
+ // src/react/sheet/useAccessSummary.ts
1355
+ var import_core10 = require("@ai-matrx/sharing/core");
1356
+ function useAccessSummary({
1357
+ entityType,
1358
+ entityId,
1359
+ enabled = true,
1360
+ refreshToken
1361
+ }) {
1362
+ const read = useStoreRead(
1363
+ entityId ? `sharing.access-summary:${entityType}:${entityId}` : null,
1364
+ () => (0, import_core10.fetchAccessSummary)(entityType, entityId),
1365
+ { enabled: enabled && Boolean(entityId), staleAfterMs: 6e4 }
1366
+ );
1367
+ useRefreshWhenChanged(refreshToken, read.refresh);
1368
+ const active = enabled && Boolean(entityId);
1369
+ const reload = () => {
1370
+ void read.refresh();
1371
+ };
1372
+ return {
1373
+ summary: active ? read.data ?? null : null,
1374
+ loading: active && read.isLoading,
1375
+ error: active && read.isError ? read.error ?? "Could not load access" : null,
1376
+ reload
1377
+ };
1378
+ }
1379
+
1380
+ // src/react/sheet/AccessSummaryPanel.tsx
1381
+ var import_core11 = require("@ai-matrx/sharing/core");
1382
+ var import_cn2 = require("@ai-matrx/design-system/cn");
1383
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1384
+ function AccessSummaryPanel({
1385
+ entityType,
1386
+ entityId,
1387
+ enabled = true,
1388
+ refreshToken,
1389
+ className,
1390
+ copy,
1391
+ onSummaryChange
1392
+ }) {
1393
+ const { summary, loading, error, reload } = useAccessSummary({
1394
+ entityType,
1395
+ entityId,
1396
+ enabled,
1397
+ refreshToken
1398
+ });
1399
+ const notifyRef = (0, import_react5.useRef)(onSummaryChange);
1400
+ (0, import_react5.useEffect)(() => {
1401
+ notifyRef.current = onSummaryChange;
1402
+ }, [onSummaryChange]);
1403
+ (0, import_react5.useEffect)(() => {
1404
+ notifyRef.current?.({ summary, error, loading });
1405
+ }, [summary, error, loading]);
1406
+ const context = copy ?? {
1407
+ resourceType: entityType,
1408
+ resourceId: entityId ?? "unknown",
1409
+ surface: "Access summary",
1410
+ kpis: (0, import_core11.accessKpis)({
1411
+ permissions: [],
1412
+ isPublic: summary?.isPublic ?? false,
1413
+ viewerIsOwner: summary ? summary.viewerIsOwner : null,
1414
+ summary,
1415
+ entityType
1416
+ })
1417
+ };
1418
+ const location = (0, import_core11.sharingLocation)(context.surface);
1419
+ const resourceAttributes = {
1420
+ resource_type: context.resourceType,
1421
+ resource_id: context.resourceId,
1422
+ resource_name: context.resourceName ?? null
1423
+ };
1424
+ if (loading && !summary) {
1425
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1426
+ "div",
1427
+ {
1428
+ className: (0, import_cn2.cn)(
1429
+ "flex items-center gap-2 px-3 py-2 text-[13px] text-muted-foreground",
1430
+ className
1431
+ ),
1432
+ children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
1434
+ "Working out who can see this\u2026"
1435
+ ]
1436
+ }
1437
+ );
1438
+ }
1439
+ if (error) {
1440
+ const errorHuman = () => [
1441
+ `${import_core11.ACCESS_SUMMARY_ERROR_HEADLINE}`,
1442
+ error,
1443
+ `Entity: ${entityType}:${entityId ?? "(none)"}`,
1444
+ `The panel offers a Retry; the reachability answer is UNKNOWN, not "private".`
1445
+ ].join("\n");
1446
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1447
+ "div",
1448
+ {
1449
+ className: (0, import_cn2.cn)(
1450
+ "group rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12px] text-destructive-ink",
1451
+ className
1452
+ ),
1453
+ children: [
1454
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "flex items-center gap-1.5 font-medium", children: [
1455
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertTriangle, { className: "h-3.5 w-3.5" }),
1456
+ import_core11.ACCESS_SUMMARY_ERROR_HEADLINE,
1457
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ml-auto opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1458
+ CopyButtons,
1459
+ {
1460
+ size: "xs",
1461
+ label: "Access summary error",
1462
+ human: errorHuman,
1463
+ json: () => ({
1464
+ error,
1465
+ entity_type: entityType,
1466
+ entity_id: entityId
1467
+ }),
1468
+ agent: () => ({
1469
+ kind: "access-summary-error",
1470
+ location,
1471
+ description: "The reachability summary failed to load. This is the error the user is looking at, verbatim \u2014 the access answer is UNKNOWN, not private.",
1472
+ data: {
1473
+ rendered_headline: import_core11.ACCESS_SUMMARY_ERROR_HEADLINE,
1474
+ rendered_error: error,
1475
+ entity_type: entityType,
1476
+ entity_id: entityId,
1477
+ retry_offered: true,
1478
+ kpis: context.kpis
1479
+ },
1480
+ summary: errorHuman(),
1481
+ attributes: {
1482
+ ...context.kpis,
1483
+ ...resourceAttributes,
1484
+ state: "error"
1485
+ }
1486
+ })
1487
+ }
1488
+ ) })
1489
+ ] }),
1490
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "opacity-80", children: error }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1492
+ "button",
1493
+ {
1494
+ type: "button",
1495
+ onClick: reload,
1496
+ className: "mt-1 underline hover:no-underline",
1497
+ children: "Retry"
1498
+ }
1499
+ ),
1500
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ErrorAlchemyMenu, { error })
1501
+ ]
1502
+ }
1503
+ );
1504
+ }
1505
+ if (!summary) return null;
1506
+ const Icon = summary.isPublic ? import_lucide_react3.Globe : (0, import_core11.isPrivateSummary)(summary) ? import_lucide_react3.Lock : import_lucide_react3.Users;
1507
+ const reasons = (0, import_core11.accessReasonRows)(summary, entityType);
1508
+ const view = () => (0, import_core11.accessSummaryView)(summary, entityType);
1509
+ const panelHuman = () => (0, import_core11.humanAccessSummary)(summary, entityType);
1510
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: (0, import_cn2.cn)("group space-y-2 px-3 py-2", className), children: [
1511
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
1512
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-[12px] text-muted-foreground", children: [
1513
+ "Who can open it:",
1514
+ " ",
1515
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-foreground", children: (0, import_core11.summaryReachLabel)(summary) })
1516
+ ] }),
1517
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ml-auto flex items-center gap-1 opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1518
+ CopyButtons,
1519
+ {
1520
+ size: "xs",
1521
+ label: "Who can see this",
1522
+ human: panelHuman,
1523
+ json: view,
1524
+ agent: () => ({
1525
+ kind: "access-summary",
1526
+ location,
1527
+ description: 'Every reason this entity is reachable, exactly as rendered: who can open it, the one-line headline, and each reason row. This is the complete answer to "who can see this, and why" \u2014 direct grants are only one of the reasons listed.',
1528
+ data: { ...view(), kpis: context.kpis },
1529
+ summary: panelHuman(),
1530
+ attributes: {
1531
+ ...context.kpis,
1532
+ ...resourceAttributes,
1533
+ reasons: reasons.length,
1534
+ is_public: summary.isPublic,
1535
+ viewer_is_owner: summary.viewerIsOwner,
1536
+ can_manage: summary.canManage
1537
+ }
1538
+ }),
1539
+ export: {
1540
+ items: [
1541
+ jsonExportItem(view, "JSON (rendered summary)"),
1542
+ csvExportItem(
1543
+ () => (0, import_core11.reasonCsvRows)(reasons),
1544
+ "CSV (all access reasons)"
1545
+ )
1546
+ ]
1547
+ }
1548
+ }
1549
+ ) })
1550
+ ] }),
1551
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "flex items-start gap-2 text-[13px] text-foreground", children: [
1552
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: view().headline })
1554
+ ] }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "space-y-1 text-[12px] text-muted-foreground", children: reasons.map((reason) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1556
+ ReasonRow,
1557
+ {
1558
+ reason,
1559
+ location,
1560
+ attributes: { ...context.kpis, ...resourceAttributes }
1561
+ },
1562
+ reason.id
1563
+ )) }),
1564
+ (0, import_core11.isPrivateSummary)(summary) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[12px] text-muted-foreground", children: import_core11.NOTHING_ELSE_GRANTS }) : null
1565
+ ] });
1566
+ }
1567
+ var REASON_ICON = {
1568
+ organization: import_lucide_react3.Users,
1569
+ "direct-grants": import_lucide_react3.Users,
1570
+ container: import_lucide_react3.Boxes
1571
+ };
1572
+ function ReasonRow({
1573
+ reason,
1574
+ location,
1575
+ attributes
1576
+ }) {
1577
+ const Icon = REASON_ICON[reason.kind];
1578
+ const human = () => `${reason.title}${reason.detail ? ` \u2014 ${reason.detail}` : ""}`;
1579
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "group/reason flex items-start gap-2", children: [
1580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "min-w-0", children: [
1582
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-foreground", children: reason.title }),
1583
+ reason.detail ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "opacity-80", children: [
1584
+ " \u2014 ",
1585
+ reason.detail
1586
+ ] }) : null
1587
+ ] }),
1588
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ml-auto shrink-0 opacity-0 transition-opacity focus-within:opacity-100 group-hover/reason:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1589
+ CopyButtons,
1590
+ {
1591
+ size: "xs",
1592
+ label: `Access reason \u2014 ${reason.title}`,
1593
+ human,
1594
+ json: () => reason,
1595
+ agent: () => ({
1596
+ kind: "access-reason",
1597
+ location,
1598
+ description: "One reason this entity is reachable, as rendered in the access summary. Removing this reason is one of the levers for changing who can see it.",
1599
+ data: reason,
1600
+ summary: human(),
1601
+ attributes: {
1602
+ ...attributes,
1603
+ reason_kind: reason.kind,
1604
+ container_type: reason.containerType ?? null,
1605
+ container_id: reason.containerId ?? null
1606
+ }
1607
+ })
1608
+ }
1609
+ ) })
1610
+ ] });
1611
+ }
1612
+
1613
+ // src/react/sheet/ShareWithUserTab.tsx
1614
+ var import_react7 = require("react");
1615
+ var import_design_system14 = require("@ai-matrx/design-system");
1616
+ var import_design_system15 = require("@ai-matrx/design-system");
1617
+ var import_design_system16 = require("@ai-matrx/design-system");
1618
+ var import_controls4 = require("@ai-matrx/design-system/controls");
1619
+
1620
+ // src/react/sheet/ui/scroll-area.tsx
1621
+ var import_design_system8 = require("@ai-matrx/design-system");
1622
+
1623
+ // src/react/sheet/ui/avatar.tsx
1624
+ var import_design_system9 = require("@ai-matrx/design-system");
1625
+
1626
+ // src/react/sheet/ShareWithUserTab.tsx
1627
+ var import_lucide_react5 = require("lucide-react");
1628
+ var import_core16 = require("@ai-matrx/sharing/core");
1629
+ var import_core17 = require("@ai-matrx/sharing/core");
1630
+ var import_core18 = require("@ai-matrx/sharing/core");
1631
+ var import_format = require("@ai-matrx/kit/format");
1632
+
1633
+ // src/react/sheet/AddEveryoneInOrg.tsx
1634
+ var import_core12 = require("@ai-matrx/sharing/core");
1635
+ var import_core13 = require("@ai-matrx/sharing/core");
1636
+ var import_react6 = require("react");
1637
+ var import_lucide_react4 = require("lucide-react");
1638
+ var import_design_system10 = require("@ai-matrx/design-system");
1639
+ var import_design_system11 = require("@ai-matrx/design-system");
1640
+ var import_design_system12 = require("@ai-matrx/design-system");
1641
+ var import_design_system13 = require("@ai-matrx/design-system");
1642
+ var import_controls3 = require("@ai-matrx/design-system/controls");
1643
+ var import_core14 = require("@ai-matrx/sharing/core");
1644
+ var import_core15 = require("@ai-matrx/sharing/core");
1645
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1646
+ var EVERYONE = "__everyone__";
1647
+ async function readTeamMembers(teamId) {
1648
+ const rows = await (0, import_core13.listTeamMembers)(teamId);
1649
+ return rows.map((row) => ({
1650
+ userId: row.userId,
1651
+ email: row.email ?? "",
1652
+ name: row.displayName || row.email || "Unnamed person"
1653
+ }));
1654
+ }
1655
+ async function readMembers(orgId) {
1656
+ const data = await (0, import_core12.readOrganizationMemberRows)(orgId);
1657
+ return data.map((row) => ({
1658
+ userId: row.user_id,
1659
+ email: row.user_email ?? "",
1660
+ name: row.user_display_name || row.user_email || "Unnamed person"
1661
+ }));
1662
+ }
1663
+ function AddEveryoneInOrg({
1664
+ grantPerson,
1665
+ level: sectionLevel,
1666
+ whyNotLevel,
1667
+ alreadySharedUserIds = [],
1668
+ onDone,
1669
+ defaultOrgId
1670
+ }) {
1671
+ const { orgs } = useOrganizationsToCreateIn();
1672
+ const detail = (0, import_core14.organizationDisambiguators)(orgs);
1673
+ const me = useCurrentUserId();
1674
+ const [open, setOpen] = (0, import_react6.useState)(false);
1675
+ const [orgId, setOrgId] = (0, import_react6.useState)("");
1676
+ const [members, setMembers] = (0, import_react6.useState)(null);
1677
+ const [loadError, setLoadError] = (0, import_react6.useState)(null);
1678
+ const [ticked, setTicked] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
1679
+ const [running, setRunning] = (0, import_react6.useState)(false);
1680
+ const [outcomes, setOutcomes] = (0, import_react6.useState)({});
1681
+ const [teams, setTeams] = (0, import_react6.useState)([]);
1682
+ const [teamsError, setTeamsError] = (0, import_react6.useState)(null);
1683
+ const [group, setGroup] = (0, import_react6.useState)(EVERYONE);
1684
+ const [ownLevel, setOwnLevel] = (0, import_react6.useState)(null);
1685
+ const level = ownLevel ?? sectionLevel;
1686
+ const orgLabel = orgs.find((o) => o.id === orgId)?.name ?? "this organization";
1687
+ const team = group === EVERYONE ? void 0 : teams.find((t) => t.id === group);
1688
+ const orgName = team ? team.name : orgLabel;
1689
+ (0, import_react6.useEffect)(() => {
1690
+ if (!open || !orgId) return;
1691
+ let cancelled = false;
1692
+ setTeams([]);
1693
+ setTeamsError(null);
1694
+ setGroup(EVERYONE);
1695
+ (0, import_core13.listTeams)(orgId).then((rows) => {
1696
+ if (!cancelled) setTeams(rows.filter((t) => t.archivedAt === null && t.memberCount > 0));
1697
+ }).catch((err) => {
1698
+ if (!cancelled) setTeamsError(err instanceof Error ? err.message : "The teams could not be listed.");
1699
+ });
1700
+ return () => {
1701
+ cancelled = true;
1702
+ };
1703
+ }, [open, orgId]);
1704
+ const already = new Set(alreadySharedUserIds);
1705
+ (0, import_react6.useEffect)(() => {
1706
+ if (!open || !orgId) return;
1707
+ let cancelled = false;
1708
+ setMembers(null);
1709
+ setLoadError(null);
1710
+ setOutcomes({});
1711
+ (group === EVERYONE ? readMembers(orgId) : readTeamMembers(group)).then((rows) => {
1712
+ if (cancelled) return;
1713
+ const others = rows.filter((m) => m.userId !== me);
1714
+ setMembers(others);
1715
+ setTicked(
1716
+ new Set(others.filter((m) => !already.has(m.userId)).map((m) => m.userId))
1717
+ );
1718
+ }).catch((err) => {
1719
+ if (!cancelled)
1720
+ setLoadError(
1721
+ err instanceof Error ? err.message : "The member list could not be read."
1722
+ );
1723
+ });
1724
+ return () => {
1725
+ cancelled = true;
1726
+ };
1727
+ }, [open, orgId, group, me]);
1728
+ const toggle = (userId, on) => setTicked((prev) => {
1729
+ const next = new Set(prev);
1730
+ if (on) next.add(userId);
1731
+ else next.delete(userId);
1732
+ return next;
1733
+ });
1734
+ const chosen = (members ?? []).filter((m) => ticked.has(m.userId));
1735
+ const run = async () => {
1736
+ setRunning(true);
1737
+ const next = {};
1738
+ let granted = 0;
1739
+ for (const person of chosen) {
1740
+ try {
1741
+ const result = await grantPerson(person, level);
1742
+ next[person.userId] = result.success ? { ok: true, say: "Shared" } : { ok: false, say: result.error || "Not shared" };
1743
+ if (result.success) granted += 1;
1744
+ } catch (err) {
1745
+ next[person.userId] = {
1746
+ ok: false,
1747
+ say: err instanceof Error ? err.message : "Not shared"
1748
+ };
1749
+ }
1750
+ setOutcomes({ ...next });
1751
+ }
1752
+ setRunning(false);
1753
+ setTicked(/* @__PURE__ */ new Set());
1754
+ if (granted > 0) onDone?.();
1755
+ };
1756
+ if (orgs.length === 0) return null;
1757
+ if (!open) {
1758
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1759
+ import_design_system10.Button,
1760
+ {
1761
+ type: "button",
1762
+ variant: "outline",
1763
+ className: "w-full h-9",
1764
+ onClick: () => {
1765
+ if (!orgId && defaultOrgId && orgs.some((o) => o.id === defaultOrgId))
1766
+ setOrgId(defaultOrgId);
1767
+ setOpen(true);
1768
+ },
1769
+ children: [
1770
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.Users, { className: "w-4 h-4 mr-2" }),
1771
+ "Add everyone in an organization or team"
1772
+ ]
1773
+ }
1774
+ );
1775
+ }
1776
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1777
+ "div",
1778
+ {
1779
+ className: "space-y-2.5 p-3 bg-muted/30 rounded-lg border",
1780
+ "data-add-everyone-in-org": true,
1781
+ children: [
1782
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1783
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("h3", { className: "type-title mb-1", children: [
1784
+ "Add everyone in ",
1785
+ orgId ? orgName : "an organization"
1786
+ ] }),
1787
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "type-secondary text-muted-foreground", children: "Each person below is given access by name. People who join later are not added." })
1788
+ ] }),
1789
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-1.5", children: [
1790
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system12.Label, { htmlFor: "add-everyone-org", className: "text-xs", children: "Organization" }),
1791
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system13.Select, { value: orgId, onValueChange: setOrgId, disabled: running, children: [
1792
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_controls3.SelectTrigger, { id: "add-everyone-org", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectValue, { placeholder: "Choose one of your organizations" }) }),
1793
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectContent, { children: orgs.map((org) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectItem, { value: org.id, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
1794
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.Building2, { className: "w-3 h-3" }),
1795
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: org.name }),
1796
+ detail.get(org.id) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "type-meta text-muted-foreground", children: detail.get(org.id) })
1797
+ ] }) }, org.id)) })
1798
+ ] })
1799
+ ] }),
1800
+ orgId && teams.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-1.5", children: [
1801
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system12.Label, { htmlFor: "add-everyone-group", className: "text-xs", children: "Who" }),
1802
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system13.Select, { value: group, onValueChange: setGroup, disabled: running, children: [
1803
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_controls3.SelectTrigger, { id: "add-everyone-group", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectValue, {}) }),
1804
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system13.SelectContent, { children: [
1805
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system13.SelectItem, { value: EVERYONE, children: [
1806
+ "Everyone in ",
1807
+ orgLabel
1808
+ ] }),
1809
+ teams.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system13.SelectItem, { value: t.id, children: [
1810
+ t.name,
1811
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "type-meta text-muted-foreground", children: [
1812
+ " \xB7 ",
1813
+ t.memberCount
1814
+ ] })
1815
+ ] }, t.id))
1816
+ ] })
1817
+ ] })
1818
+ ] }) : null,
1819
+ orgId ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-1.5", children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system12.Label, { htmlFor: "add-everyone-level", className: "text-xs", children: "They get" }),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1822
+ import_design_system13.Select,
1823
+ {
1824
+ value: level,
1825
+ onValueChange: (v) => setOwnLevel(v),
1826
+ disabled: running,
1827
+ children: [
1828
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_controls3.SelectTrigger, { id: "add-everyone-level", "data-add-everyone-level": level, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectValue, {}) }),
1829
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectContent, { children: import_core15.PERMISSION_LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system13.SelectItem, { value: l, disabled: (whyNotLevel?.(l) ?? null) !== null, title: whyNotLevel?.(l) ?? void 0, children: import_core15.PERMISSION_LEVEL_SHORT_LABELS[l] }, l)) })
1830
+ ]
1831
+ }
1832
+ )
1833
+ ] }) : null,
1834
+ orgId && teamsError ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "type-secondary text-destructive", children: [
1835
+ "The teams in ",
1836
+ orgLabel,
1837
+ " could not be listed: ",
1838
+ teamsError,
1839
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorAlchemyMenu, { error: teamsError })
1840
+ ] }) : null,
1841
+ orgId && loadError ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "type-secondary text-destructive", children: [
1842
+ "The people in ",
1843
+ orgName,
1844
+ " could not be listed: ",
1845
+ loadError,
1846
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorAlchemyMenu, { error: loadError })
1847
+ ] }) : null,
1848
+ orgId && !loadError && members === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2 type-secondary text-muted-foreground", children: [
1849
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.Loader2, { className: "w-3 h-3 animate-spin" }),
1850
+ " Listing the people in",
1851
+ " ",
1852
+ orgName,
1853
+ "\u2026"
1854
+ ] }) : null,
1855
+ members && members.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "type-secondary text-muted-foreground", children: [
1856
+ "Nobody else is in ",
1857
+ orgName,
1858
+ "."
1859
+ ] }) : null,
1860
+ members && members.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "space-y-1 max-h-56 overflow-y-auto", "aria-label": `People in ${orgName}`, children: members.map((m) => {
1861
+ const has = already.has(m.userId);
1862
+ const outcome = outcomes[m.userId];
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1864
+ "li",
1865
+ {
1866
+ className: "flex items-center gap-2 type-body",
1867
+ "data-member": m.email,
1868
+ children: [
1869
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1870
+ import_design_system11.Checkbox,
1871
+ {
1872
+ size: "sm",
1873
+ id: `add-everyone-${m.userId}`,
1874
+ checked: ticked.has(m.userId),
1875
+ disabled: running || has || !!outcome?.ok,
1876
+ onCheckedChange: (v) => toggle(m.userId, v === true)
1877
+ }
1878
+ ),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1880
+ "label",
1881
+ {
1882
+ htmlFor: `add-everyone-${m.userId}`,
1883
+ className: "flex-1 min-w-0",
1884
+ children: [
1885
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate", children: m.name }),
1886
+ m.email && m.email !== m.name ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate type-secondary text-muted-foreground", children: m.email }) : null
1887
+ ]
1888
+ }
1889
+ ),
1890
+ has && !outcome ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "type-secondary text-muted-foreground", children: "Already has access" }) : null,
1891
+ outcome ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1892
+ "span",
1893
+ {
1894
+ className: `flex items-center gap-1 text-xs ${outcome.ok ? "text-green-700 dark:text-green-400" : "text-destructive"}`,
1895
+ children: [
1896
+ outcome.ok ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.CheckCircle, { className: "w-3 h-3" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.XCircle, { className: "w-3 h-3" }),
1897
+ outcome.say
1898
+ ]
1899
+ }
1900
+ ) : null
1901
+ ]
1902
+ },
1903
+ m.userId
1904
+ );
1905
+ }) }) : null,
1906
+ members && members.length > 0 && chosen.length === 0 && !running ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "type-secondary text-muted-foreground", children: Object.values(outcomes).some((o) => o.ok) || members.every((m) => already.has(m.userId)) ? `Everyone listed in ${orgName} has access now.` : "Tick the people to add." }) : null,
1907
+ members && members.length > 0 && (chosen.length > 0 || running) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1908
+ import_design_system10.Button,
1909
+ {
1910
+ type: "button",
1911
+ onClick: run,
1912
+ disabled: running || chosen.length === 0,
1913
+ className: "w-full h-9",
1914
+ children: running ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.Loader2, { className: "w-4 h-4 mr-2 animate-spin" }),
1916
+ "Sharing\u2026"
1917
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1918
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react4.Users, { className: "w-4 h-4 mr-2" }),
1919
+ "Share with ",
1920
+ chosen.length,
1921
+ " ",
1922
+ chosen.length === 1 ? "person" : "people"
1923
+ ] })
1924
+ }
1925
+ ) : null
1926
+ ]
1927
+ }
1928
+ );
1929
+ }
1930
+
1931
+ // src/react/sheet/ShareWithUserTab.tsx
1932
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1933
+ var SOURCE_ICONS = {
1934
+ conversation: import_lucide_react5.MessageSquare,
1935
+ organization: import_lucide_react5.Building2,
1936
+ invitation: import_lucide_react5.Mail
1937
+ };
1938
+ var SOURCE_LABELS = {
1939
+ conversation: "Contact",
1940
+ organization: "Organization",
1941
+ invitation: "Invited"
1942
+ };
1943
+ var EMAIL_SHAPE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1944
+ async function lookupConnectionByEmail(typed) {
1945
+ const { data } = await (0, import_core17.createClient)().rpc("lookup_user_by_email", { lookup_email: typed });
1946
+ const row = data?.[0];
1947
+ return row ? {
1948
+ user_id: row.user_id,
1949
+ email: row.user_email,
1950
+ display_name: row.user_email.split("@", 1)[0] ?? null,
1951
+ avatar_url: null,
1952
+ source: "invitation",
1953
+ sourceDetails: "Found by email"
1954
+ } : null;
1955
+ }
1956
+ function candidateOf(connection, foundByEmail) {
1957
+ return {
1958
+ id: connection.user_id,
1959
+ email: connection.email,
1960
+ displayName: connection.display_name,
1961
+ avatarUrl: connection.avatar_url,
1962
+ phone: null,
1963
+ adminLevel: null,
1964
+ organizations: connection.source === "organization" && connection.sourceDetails ? [connection.sourceDetails] : [],
1965
+ source: foundByEmail ? "Found by email" : SOURCE_LABELS[connection.source],
1966
+ createdAt: null,
1967
+ lastSignInAt: null
1968
+ };
1969
+ }
1970
+ function ShareWithUserTab({
1971
+ levelWording = "roles",
1972
+ grantable,
1973
+ onShare,
1974
+ onSuccess,
1975
+ resourceType,
1976
+ resourceId,
1977
+ organizationId,
1978
+ copy,
1979
+ grantEveryonePerson,
1980
+ alreadySharedUserIds
1981
+ }) {
1982
+ const [email, setEmail] = (0, import_react7.useState)("");
1983
+ const [searchQuery, setSearchQuery] = (0, import_react7.useState)("");
1984
+ const [permissionLevel, setPermissionLevel] = (0, import_react7.useState)("viewer");
1985
+ const whyNotLevel = (level) => {
1986
+ const why = grantable?.levels[level];
1987
+ return typeof why === "string" ? why : null;
1988
+ };
1989
+ const chosenWhy = whyNotLevel(permissionLevel);
1990
+ const [status, setStatus] = (0, import_react7.useState)({ type: "idle", message: "" });
1991
+ const [selectedContact, setSelectedContact] = (0, import_react7.useState)(
1992
+ null
1993
+ );
1994
+ const {
1995
+ connections,
1996
+ isLoading: connectionsLoading,
1997
+ error: connectionsError,
1998
+ partialFailures: connectionsPartial,
1999
+ refresh: refreshConnections
2000
+ } = useUserConnections(organizationId ? { organizationId } : {});
2001
+ const filteredConnections = (0, import_react7.useMemo)(() => {
2002
+ if (!searchQuery.trim()) return connections;
2003
+ const q = searchQuery.toLowerCase();
2004
+ return connections.filter(
2005
+ (c) => c.display_name && c.display_name.toLowerCase().includes(q) || c.email && c.email.toLowerCase().includes(q)
2006
+ );
2007
+ }, [connections, searchQuery]);
2008
+ const [looked, setLooked] = (0, import_react7.useState)(null);
2009
+ const resolveTypedEmail = async (query) => {
2010
+ const typed = query.trim().toLowerCase();
2011
+ if (!EMAIL_SHAPE.test(typed)) return [];
2012
+ const found = connections.find((c) => c.email?.toLowerCase() === typed) ?? await lookupConnectionByEmail(typed);
2013
+ return found ? [candidateOf(found, true)] : [];
2014
+ };
2015
+ (0, import_react7.useEffect)(() => {
2016
+ const typed = email.trim().toLowerCase();
2017
+ if (!EMAIL_SHAPE.test(typed)) {
2018
+ setLooked(null);
2019
+ return;
2020
+ }
2021
+ if (connections.some((c) => c.email?.toLowerCase() === typed)) {
2022
+ setLooked(null);
2023
+ return;
2024
+ }
2025
+ let stale = false;
2026
+ const handle = setTimeout(async () => {
2027
+ const found = await lookupConnectionByEmail(typed);
2028
+ if (stale) return;
2029
+ setLooked(found);
2030
+ }, 300);
2031
+ return () => {
2032
+ stale = true;
2033
+ clearTimeout(handle);
2034
+ };
2035
+ }, [email, connections]);
2036
+ const searchableConnections = (0, import_react7.useMemo)(
2037
+ () => looked ? [...connections, looked] : connections,
2038
+ [connections, looked]
2039
+ );
2040
+ const userSearchCandidates = searchableConnections.map(
2041
+ (connection) => candidateOf(connection, connection === looked)
2042
+ );
2043
+ const resetStatus = () => {
2044
+ setStatus({ type: "idle", message: "" });
2045
+ };
2046
+ const selectContact = (contact) => {
2047
+ setSelectedContact(contact);
2048
+ setEmail(contact.email || "");
2049
+ setSearchQuery("");
2050
+ if (status.type === "error") resetStatus();
2051
+ };
2052
+ const clearContact = () => {
2053
+ setSelectedContact(null);
2054
+ setEmail("");
2055
+ };
2056
+ const handleShare = async () => {
2057
+ const trimmedEmail = email.trim();
2058
+ if (!trimmedEmail || !trimmedEmail.includes("@")) {
2059
+ setStatus({
2060
+ type: "error",
2061
+ message: "Please enter a valid email address"
2062
+ });
2063
+ return;
2064
+ }
2065
+ setStatus({ type: "loading", message: "Looking up user..." });
2066
+ try {
2067
+ let targetUserId = null;
2068
+ if (selectedContact && selectedContact.email === trimmedEmail) {
2069
+ targetUserId = selectedContact.user_id;
2070
+ } else {
2071
+ const client = (0, import_core17.createClient)();
2072
+ const { data: lookupData, error: lookupError } = await client.rpc(
2073
+ "lookup_user_by_email",
2074
+ { lookup_email: trimmedEmail }
2075
+ );
2076
+ if (lookupError) {
2077
+ setStatus({
2078
+ type: "error",
2079
+ message: `Lookup failed: ${lookupError.message}`
2080
+ });
2081
+ return;
2082
+ }
2083
+ const userData = Array.isArray(lookupData) ? lookupData[0] : lookupData;
2084
+ if ((!userData || !userData.user_id) && !grantEveryonePerson) {
2085
+ setStatus({
2086
+ type: "error",
2087
+ message: `No user found with email "${trimmedEmail}". They may need to create an account first.`
2088
+ });
2089
+ return;
2090
+ }
2091
+ targetUserId = userData?.user_id ?? null;
2092
+ }
2093
+ setStatus({ type: "loading", message: "Sharing with user..." });
2094
+ const result = grantEveryonePerson ? await grantEveryonePerson(
2095
+ { userId: targetUserId ?? "", email: trimmedEmail, name: trimmedEmail.split("@", 1)[0] ?? trimmedEmail },
2096
+ permissionLevel
2097
+ ) : await onShare(targetUserId, permissionLevel);
2098
+ const errorMsg = result?.error;
2099
+ if (!errorMsg) {
2100
+ setStatus({
2101
+ type: "success",
2102
+ // THE CONFIRMATION LIVES HERE, IN THE DIALOG THAT DID IT (law 4: a sentence with an
2103
+ // inline home is not also toasted). It used to ride a legacy Radix toast, and on a
2104
+ // table page the organization gate's pick closed this dialog before anyone read it
2105
+ // (VERIFIER-18 H4) — the dialog now stays open, saying it worked.
2106
+ message: `Shared with ${trimmedEmail}. They can open it now.`
2107
+ });
2108
+ setEmail("");
2109
+ setSelectedContact(null);
2110
+ setPermissionLevel("viewer");
2111
+ setTimeout(() => {
2112
+ onSuccess();
2113
+ }, 1500);
2114
+ } else {
2115
+ setStatus({ type: "error", message: errorMsg });
2116
+ }
2117
+ } catch (err) {
2118
+ const message = err instanceof Error ? err.message : "Something went wrong";
2119
+ setStatus({ type: "error", message: `Unexpected error: ${message}` });
2120
+ }
2121
+ };
2122
+ const isLoading = status.type === "loading";
2123
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-2.5 p-3 bg-muted/30 rounded-lg border", children: [
2124
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2125
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "text-sm font-medium mb-1", children: "Share with User" }),
2126
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-muted-foreground", children: "Select a contact or enter an email address" })
2127
+ ] }),
2128
+ status.type !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2129
+ "div",
2130
+ {
2131
+ className: `p-2 rounded-md flex items-start gap-2 text-xs ${status.type === "loading" ? "bg-blue-500/10 text-blue-700 dark:text-blue-300 border border-blue-500/20" : status.type === "success" ? "bg-green-500/10 text-green-700 dark:text-green-300 border border-green-500/20" : "bg-red-500/10 text-red-700 dark:text-red-300 border border-red-500/20"}`,
2132
+ children: [
2133
+ status.type === "loading" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Loader2, { className: "w-3.5 h-3.5 animate-spin flex-shrink-0 mt-0.5" }),
2134
+ status.type === "success" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.CheckCircle, { className: "w-3.5 h-3.5 flex-shrink-0 mt-0.5" }),
2135
+ status.type === "error" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.XCircle, { className: "w-3.5 h-3.5 flex-shrink-0 mt-0.5" }),
2136
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "flex-1", children: status.message }),
2137
+ status.type === "error" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2138
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2139
+ CopyButtons,
2140
+ {
2141
+ size: "xs",
2142
+ label: "Share failure",
2143
+ human: () => [
2144
+ "Sharing with a user failed.",
2145
+ `Error shown: ${status.message}`,
2146
+ `Email entered: ${email.trim() || "(empty)"}`,
2147
+ `Contact selected: ${selectedContact ? `${selectedContact.display_name ?? selectedContact.email} (${selectedContact.source})` : "none"}`,
2148
+ `Permission level chosen: ${permissionLevel}`,
2149
+ `Resource: ${resourceType}:${resourceId}`
2150
+ ].join("\n"),
2151
+ json: () => ({
2152
+ error: status.message,
2153
+ email: email.trim(),
2154
+ selected_contact: selectedContact,
2155
+ permission_level: permissionLevel,
2156
+ resource_type: resourceType,
2157
+ resource_id: resourceId
2158
+ }),
2159
+ agent: () => ({
2160
+ kind: "share-with-user-failure",
2161
+ location: (0, import_core18.sharingLocation)(copy?.surface ?? "Share with user"),
2162
+ description: "A share-with-user attempt that failed, with the error sentence on screen and the live form state that produced it.",
2163
+ data: {
2164
+ rendered_error: status.message,
2165
+ form_state: {
2166
+ email_entered: email.trim(),
2167
+ selected_contact: selectedContact,
2168
+ permission_level: permissionLevel,
2169
+ contacts_available: connections.length
2170
+ },
2171
+ resource: {
2172
+ type: resourceType,
2173
+ id: resourceId,
2174
+ name: copy?.resourceName ?? null
2175
+ },
2176
+ kpis: copy?.kpis ?? null
2177
+ },
2178
+ attributes: {
2179
+ ...copy?.kpis ?? {},
2180
+ resource_type: resourceType,
2181
+ resource_id: resourceId,
2182
+ state: "error",
2183
+ permission_level: permissionLevel
2184
+ }
2185
+ })
2186
+ }
2187
+ ),
2188
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2189
+ "button",
2190
+ {
2191
+ onClick: resetStatus,
2192
+ className: "text-xs underline hover:no-underline flex-shrink-0",
2193
+ children: "Dismiss"
2194
+ }
2195
+ )
2196
+ ] })
2197
+ ]
2198
+ }
2199
+ ),
2200
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-2.5", children: [
2201
+ !selectedContact && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-1.5", children: [
2202
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_design_system15.Label, { className: "text-xs flex items-center gap-1.5", children: [
2203
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Users, { className: "w-3 h-3" }),
2204
+ "Your Contacts"
2205
+ ] }),
2206
+ connectionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-center py-4 text-xs text-muted-foreground", children: [
2207
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Loader2, { className: "w-3.5 h-3.5 animate-spin mr-2" }),
2208
+ "Loading contacts..."
2209
+ ] }) : connectionsError && connections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2210
+ ConnectionsReadNotice,
2211
+ {
2212
+ error: connectionsError,
2213
+ partialFailures: connectionsPartial,
2214
+ hasRows: false,
2215
+ onRetry: () => void refreshConnections()
2216
+ }
2217
+ ) : connections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-muted-foreground py-2 text-center", children: "No contacts found. Enter an email below." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2218
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2219
+ ConnectionsReadNotice,
2220
+ {
2221
+ error: connectionsError,
2222
+ partialFailures: connectionsPartial,
2223
+ hasRows: true,
2224
+ onRetry: () => void refreshConnections()
2225
+ }
2226
+ ),
2227
+ connections.length > 5 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2228
+ UserSearchField,
2229
+ {
2230
+ placeholder: "Search contacts...",
2231
+ value: searchQuery,
2232
+ onValueChange: setSearchQuery,
2233
+ onUserSelect: (user) => {
2234
+ const contact = searchableConnections.find(
2235
+ (candidate) => candidate.user_id === user.id
2236
+ );
2237
+ if (contact) selectContact(contact);
2238
+ },
2239
+ candidates: userSearchCandidates,
2240
+ title: "Search your contacts",
2241
+ disabled: isLoading,
2242
+ className: "w-full",
2243
+ inputClassName: "h-8 text-xs"
2244
+ }
2245
+ ) }),
2246
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system8.ScrollArea, { className: "max-h-36 rounded-md border bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "p-1", children: filteredConnections.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-muted-foreground py-2 text-center", children: "No matching contacts" }) : filteredConnections.map((contact) => {
2247
+ const SourceIcon = SOURCE_ICONS[contact.source];
2248
+ return (
2249
+ // ui-exception: moved verbatim from the web share sheet; restyling onto design-system Button is its own UI change
2250
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2251
+ "button",
2252
+ {
2253
+ onClick: () => selectContact(contact),
2254
+ disabled: isLoading,
2255
+ className: "w-full flex items-center gap-2 p-1.5 rounded-md hover:bg-accent/50 transition-colors text-left disabled:opacity-50",
2256
+ children: [
2257
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_design_system9.Avatar, { className: "w-6 h-6 flex-shrink-0", children: [
2258
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2259
+ import_design_system9.AvatarImage,
2260
+ {
2261
+ src: contact.avatar_url || void 0
2262
+ }
2263
+ ),
2264
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system9.AvatarFallback, { className: "text-[10px] bg-primary/10 text-primary-ink", children: (0, import_format.getInitials)(contact.display_name ?? contact.email) })
2265
+ ] }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex-1 min-w-0", children: [
2267
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs font-medium truncate", children: contact.display_name || contact.email || "Unknown" }),
2268
+ contact.display_name && contact.email && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-[10px] text-muted-foreground truncate", children: contact.email })
2269
+ ] }),
2270
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex items-center gap-1 text-[10px] text-muted-foreground flex-shrink-0", children: [
2271
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SourceIcon, { className: "w-3 h-3" }),
2272
+ SOURCE_LABELS[contact.source]
2273
+ ] })
2274
+ ]
2275
+ },
2276
+ contact.user_id
2277
+ )
2278
+ );
2279
+ }) }) })
2280
+ ] })
2281
+ ] }),
2282
+ selectedContact && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 p-2 rounded-md bg-primary/5 border border-primary/20", children: [
2283
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_design_system9.Avatar, { className: "w-6 h-6 flex-shrink-0", children: [
2284
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system9.AvatarImage, { src: selectedContact.avatar_url || void 0 }),
2285
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system9.AvatarFallback, { className: "text-[10px] bg-primary/10 text-primary-ink", children: (0, import_format.getInitials)(
2286
+ selectedContact.display_name ?? selectedContact.email
2287
+ ) })
2288
+ ] }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex-1 min-w-0", children: [
2290
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs font-medium truncate", children: selectedContact.display_name || selectedContact.email }),
2291
+ selectedContact.display_name && selectedContact.email && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-[10px] text-muted-foreground truncate", children: selectedContact.email })
2292
+ ] }),
2293
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2294
+ "button",
2295
+ {
2296
+ onClick: clearContact,
2297
+ disabled: isLoading,
2298
+ className: "text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50",
2299
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.XCircle, { className: "w-4 h-4" })
2300
+ }
2301
+ )
2302
+ ] }),
2303
+ !selectedContact && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-1.5", children: [
2304
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system15.Label, { htmlFor: "user-email", className: "text-xs", children: "Or enter email manually" }),
2305
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2306
+ UserSearchField,
2307
+ {
2308
+ id: "user-email",
2309
+ placeholder: "user@example.com",
2310
+ value: email,
2311
+ onValueChange: (value) => {
2312
+ setEmail(value);
2313
+ if (status.type === "error") resetStatus();
2314
+ },
2315
+ onUserSelect: (user) => {
2316
+ const contact = searchableConnections.find(
2317
+ (candidate) => candidate.user_id === user.id
2318
+ );
2319
+ if (contact) selectContact(contact);
2320
+ },
2321
+ candidates: userSearchCandidates,
2322
+ resolveCandidates: resolveTypedEmail,
2323
+ title: "Choose a user to share with",
2324
+ inputType: "email",
2325
+ disabled: isLoading,
2326
+ inputClassName: "h-9"
2327
+ }
2328
+ )
2329
+ ] }),
2330
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-1.5", children: [
2331
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system15.Label, { htmlFor: "user-permission", className: "text-xs", children: "Permission Level" }),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2333
+ import_design_system16.Select,
2334
+ {
2335
+ value: permissionLevel,
2336
+ onValueChange: (value) => setPermissionLevel(value),
2337
+ disabled: isLoading,
2338
+ children: [
2339
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_controls4.SelectTrigger, { id: "user-permission", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectValue, {}) }),
2340
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectContent, { children: levelWording === "notion" ? [...import_core16.PERMISSION_LEVELS].reverse().map((level) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectItem, { value: level, disabled: whyNotLevel(level) !== null, title: whyNotLevel(level) ?? import_core16.PERMISSION_LEVEL_HINTS[level], children: import_core16.PERMISSION_LEVEL_LABELS[level] }, level)) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2341
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectItem, { value: "viewer", disabled: whyNotLevel("viewer") !== null, title: whyNotLevel("viewer") ?? import_core16.PERMISSION_LEVEL_HINTS.viewer, children: import_core16.PERMISSION_LEVEL_SHORT_LABELS.viewer }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectItem, { value: "commenter", disabled: whyNotLevel("commenter") !== null, title: whyNotLevel("commenter") ?? import_core16.PERMISSION_LEVEL_HINTS.commenter, children: import_core16.PERMISSION_LEVEL_SHORT_LABELS.commenter }),
2343
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectItem, { value: "editor", disabled: whyNotLevel("editor") !== null, title: whyNotLevel("editor") ?? import_core16.PERMISSION_LEVEL_HINTS.editor, children: import_core16.PERMISSION_LEVEL_SHORT_LABELS.editor }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_design_system16.SelectItem, { value: "admin", disabled: whyNotLevel("admin") !== null, title: whyNotLevel("admin") ?? import_core16.PERMISSION_LEVEL_HINTS.admin, children: import_core16.PERMISSION_LEVEL_SHORT_LABELS.admin })
2345
+ ] }) })
2346
+ ]
2347
+ }
2348
+ ),
2349
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PermissionLevelDescription, { level: permissionLevel }),
2350
+ chosenWhy ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-muted-foreground", children: chosenWhy }) : null,
2351
+ !chosenWhy && whyNotLevel("admin") ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-muted-foreground", "data-share-level-reason": "", children: whyNotLevel("admin") }) : null,
2352
+ grantable?.outside ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-muted-foreground", "data-share-outside-reason": "", children: grantable.outside }) : null
2353
+ ] }),
2354
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2355
+ import_design_system14.Button,
2356
+ {
2357
+ onClick: handleShare,
2358
+ disabled: isLoading || !email.trim() || chosenWhy !== null,
2359
+ className: "w-full h-9",
2360
+ children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2361
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.Loader2, { className: "w-4 h-4 mr-2 animate-spin" }),
2362
+ status.message || "Processing..."
2363
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2364
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react5.UserPlus, { className: "w-4 h-4 mr-2" }),
2365
+ "Share with User"
2366
+ ] })
2367
+ }
2368
+ ),
2369
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2370
+ AddEveryoneInOrg,
2371
+ {
2372
+ level: permissionLevel,
2373
+ whyNotLevel,
2374
+ grantPerson: grantEveryonePerson ?? ((person, level) => onShare(person.userId, level)),
2375
+ alreadySharedUserIds: alreadySharedUserIds ?? [],
2376
+ onDone: onSuccess,
2377
+ ...organizationId ? { defaultOrgId: organizationId } : {}
2378
+ }
2379
+ )
2380
+ ] })
2381
+ ] });
2382
+ }
2383
+
2384
+ // src/react/sheet/OutsideSharePanel.tsx
2385
+ var import_react8 = require("react");
2386
+ var import_lucide_react6 = require("lucide-react");
2387
+ var import_design_system17 = require("@ai-matrx/design-system");
2388
+ var import_controls5 = require("@ai-matrx/design-system/controls");
2389
+ var import_design_system18 = require("@ai-matrx/design-system");
2390
+ var import_design_system19 = require("@ai-matrx/design-system");
2391
+ var import_controls6 = require("@ai-matrx/design-system/controls");
2392
+ var import_clipboard2 = require("@ai-matrx/kit/clipboard");
2393
+ var import_core19 = require("@ai-matrx/sharing/core");
2394
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2395
+ var LANE_CLOSED_SAY = "This organization's administrator has turned off sharing with people outside it.";
2396
+ function OutsideSharePanel({
2397
+ organizationId,
2398
+ tableId,
2399
+ tableName,
2400
+ onPendingChange,
2401
+ onGranted
2402
+ }) {
2403
+ const { toast } = useToast();
2404
+ const [state2, setState] = (0, import_react8.useState)(null);
2405
+ const [loadError, setLoadError] = (0, import_react8.useState)(null);
2406
+ const [busy, setBusy] = (0, import_react8.useState)(null);
2407
+ const [email, setEmail] = (0, import_react8.useState)("");
2408
+ const [level, setLevel] = (0, import_react8.useState)("viewer");
2409
+ const [copied, setCopied] = (0, import_react8.useState)(null);
2410
+ const refresh = (0, import_react8.useCallback)(async () => {
2411
+ setLoadError(null);
2412
+ try {
2413
+ const answer = await (0, import_core19.readOutsideShare)(organizationId, tableId);
2414
+ setState(answer);
2415
+ setLevel(
2416
+ (current) => answer.levels.some((l) => l.level === current) ? current : answer.levels[0]?.level ?? "viewer"
2417
+ );
2418
+ } catch (error) {
2419
+ setState(null);
2420
+ setLoadError(error instanceof Error ? error.message : String(error));
2421
+ }
2422
+ }, [organizationId, tableId]);
2423
+ (0, import_react8.useEffect)(() => {
2424
+ void refresh();
2425
+ }, [refresh]);
2426
+ (0, import_react8.useEffect)(() => {
2427
+ onPendingChange?.((state2?.invitations ?? []).filter((row) => !row.joined).length);
2428
+ }, [state2, onPendingChange]);
2429
+ const copyLink = (0, import_react8.useCallback)(
2430
+ async (invitationId, acceptPath, who) => {
2431
+ const url = (0, import_core19.absoluteInviteUrl)(acceptPath);
2432
+ const ok = await (0, import_clipboard2.copyText)(url).then((r) => r !== false, () => false);
2433
+ if (!ok) return;
2434
+ setCopied(invitationId);
2435
+ toast({ title: `Link copied. Send it to ${who} however you like.` });
2436
+ window.setTimeout(
2437
+ () => setCopied((c) => c === invitationId ? null : c),
2438
+ 2e3
2439
+ );
2440
+ },
2441
+ [toast]
2442
+ );
2443
+ const run = async (key, work) => {
2444
+ setBusy(key);
2445
+ try {
2446
+ const answer = await work();
2447
+ toast({ title: answer.say, description: answer.delivery?.say });
2448
+ await refresh();
2449
+ } catch (error) {
2450
+ toast({
2451
+ title: "That did not happen",
2452
+ description: error instanceof Error ? error.message : String(error),
2453
+ variant: "destructive"
2454
+ });
2455
+ } finally {
2456
+ setBusy(null);
2457
+ }
2458
+ };
2459
+ if (loadError) {
2460
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 p-3", children: [
2461
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start gap-2", children: [
2462
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.AlertTriangle, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-destructive" }),
2463
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "min-w-0", children: [
2464
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "text-sm font-medium text-destructive", children: [
2465
+ "We could not check who outside this organization can see ",
2466
+ tableName,
2467
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorAlchemyMenu, {})
2468
+ ] }),
2469
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-0.5 text-xs text-destructive/80", children: "Nobody was added or removed. This does not mean sharing outside is switched off \u2014 we simply could not look." }),
2470
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-1 break-words text-xs text-muted-foreground", children: loadError })
2471
+ ] })
2472
+ ] }),
2473
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system17.Button, { size: "sm", variant: "outline", className: "mt-2", onClick: () => void refresh(), children: "Try again" })
2474
+ ] });
2475
+ }
2476
+ if (!state2) {
2477
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", "aria-busy": "true", children: [
2478
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Loader2, { className: "h-4 w-4 animate-spin" }),
2479
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
2480
+ "Checking who outside this organization can see ",
2481
+ tableName,
2482
+ "\u2026"
2483
+ ] })
2484
+ ] });
2485
+ }
2486
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-3 rounded-lg border p-3", children: [
2487
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start gap-2", children: [
2488
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Globe2, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-muted-foreground" }),
2489
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "min-w-0", children: [
2490
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-medium", children: "People outside this organization" }),
2491
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: state2.lane_open ? state2.say : LANE_CLOSED_SAY })
2492
+ ] })
2493
+ ] }),
2494
+ state2.lane_open && state2.may_invite && state2.email_delivery?.answer !== "yes" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 p-2", children: [
2495
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.MailWarning, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" }),
2496
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-muted-foreground", children: state2.email_say })
2497
+ ] }) : null,
2498
+ state2.lane_open && state2.may_invite ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-end", children: [
2499
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "min-w-0 flex-1", children: [
2500
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system18.Label, { htmlFor: "outside-email", className: "text-xs", children: "Their email address" }),
2501
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2502
+ import_controls5.Input,
2503
+ {
2504
+ id: "outside-email",
2505
+ type: "email",
2506
+ placeholder: "name@theircompany.com",
2507
+ value: email,
2508
+ onChange: (e) => setEmail(e.target.value)
2509
+ }
2510
+ )
2511
+ ] }),
2512
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "sm:w-44", children: [
2513
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system18.Label, { htmlFor: "outside-level", className: "text-xs", children: "They can" }),
2514
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_design_system19.Select, { value: level, onValueChange: setLevel, children: [
2515
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_controls6.SelectTrigger, { id: "outside-level", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system19.SelectValue, {}) }),
2516
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system19.SelectContent, { children: state2.levels.map((l) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_design_system19.SelectItem, { value: l.level, children: l.means }, l.level)) })
2517
+ ] })
2518
+ ] }),
2519
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2520
+ import_design_system17.Button,
2521
+ {
2522
+ size: "sm",
2523
+ disabled: !email.trim() || busy === "invite",
2524
+ onClick: () => void run("invite", async () => {
2525
+ const answer = await (0, import_core19.shareWithOutsidePerson)(organizationId, tableId, email.trim(), level);
2526
+ setEmail("");
2527
+ if (answer.granted) onGranted?.();
2528
+ return answer;
2529
+ }),
2530
+ children: [
2531
+ busy === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Send, { className: "mr-1.5 h-4 w-4" }),
2532
+ "Share"
2533
+ ]
2534
+ }
2535
+ )
2536
+ ] }) : null,
2537
+ state2.invitations.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "divide-y rounded-md border", children: state2.invitations.map((row) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { className: "flex items-start gap-2 p-2", children: [
2538
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "min-w-0 flex-1", children: [
2539
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-wrap items-baseline gap-2", children: [
2540
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-medium", children: row.email }),
2541
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2542
+ "span",
2543
+ {
2544
+ className: row.joined ? "rounded-full bg-primary/10 px-1.5 py-0.5 text-xs" : "rounded-full bg-muted px-1.5 py-0.5 text-xs text-muted-foreground",
2545
+ children: row.joined ? row.level_label : "Invited, not yet joined"
2546
+ }
2547
+ )
2548
+ ] }),
2549
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: row.say })
2550
+ ] }),
2551
+ state2.may_invite ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-shrink-0 gap-1", children: [
2552
+ row.accept_path ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2553
+ import_design_system17.Button,
2554
+ {
2555
+ size: "sm",
2556
+ variant: "ghost",
2557
+ title: "Copy invitation link",
2558
+ onClick: () => void copyLink(
2559
+ row.invitation_id,
2560
+ row.accept_path,
2561
+ row.email
2562
+ ),
2563
+ children: [
2564
+ copied === row.invitation_id ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Check, { className: "mr-1.5 h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Link2, { className: "mr-1.5 h-4 w-4" }),
2565
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-xs", children: copied === row.invitation_id ? "Copied" : "Copy link" })
2566
+ ]
2567
+ }
2568
+ ) : null,
2569
+ !row.joined ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2570
+ import_design_system17.Button,
2571
+ {
2572
+ size: "sm",
2573
+ variant: "ghost",
2574
+ title: "Send a fresh link",
2575
+ disabled: busy === `resend:${row.invitation_id}`,
2576
+ onClick: () => void run(
2577
+ `resend:${row.invitation_id}`,
2578
+ () => (0, import_core19.resendOutside)(organizationId, row.invitation_id)
2579
+ ),
2580
+ children: busy === `resend:${row.invitation_id}` ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.RotateCw, { className: "h-4 w-4" })
2581
+ }
2582
+ ) : null,
2583
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2584
+ import_design_system17.Button,
2585
+ {
2586
+ size: "sm",
2587
+ variant: "ghost",
2588
+ title: row.joined ? "Take their access away" : "Withdraw the invitation",
2589
+ disabled: busy === `revoke:${row.invitation_id}`,
2590
+ onClick: () => void (async () => {
2591
+ const ok = await confirm({
2592
+ title: row.joined ? `Take ${row.email}'s access to ${tableName} away?` : `Withdraw ${row.email}'s invitation?`,
2593
+ description: row.joined ? `They lose ${tableName} immediately \u2014 anything they have open refuses the next time it asks \u2014 and, because this table is the only reason they can reach this organization at all, they lose that too. You can invite them again afterwards.` : "Their link stops working. They never had access, so there is nothing to take away.",
2594
+ confirmLabel: row.joined ? "Take it away" : "Withdraw",
2595
+ variant: "destructive"
2596
+ });
2597
+ if (!ok) return;
2598
+ await run(
2599
+ `revoke:${row.invitation_id}`,
2600
+ () => (0, import_core19.revokeOutside)(organizationId, row.invitation_id)
2601
+ );
2602
+ })(),
2603
+ children: busy === `revoke:${row.invitation_id}` ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react6.Trash2, { className: "h-4 w-4" })
2604
+ }
2605
+ )
2606
+ ] }) : null
2607
+ ] }, row.invitation_id)) }) : null
2608
+ ] });
2609
+ }
2610
+
2611
+ // src/react/sheet/ShareModal.tsx
2612
+ var import_core36 = require("@ai-matrx/sharing/core");
2613
+
2614
+ // src/react/sheet/WhoCouldSeeOn.tsx
2615
+ var import_react9 = require("react");
2616
+ var import_lucide_react7 = require("lucide-react");
2617
+ var import_design_system20 = require("@ai-matrx/design-system");
2618
+ var import_controls7 = require("@ai-matrx/design-system/controls");
2619
+ var import_core20 = require("@ai-matrx/sharing/core");
2620
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2621
+ function custom() {
2622
+ return (0, import_core20.createClient)().schema("custom");
2623
+ }
2624
+ function WhoCouldSeeOn({ recordId }) {
2625
+ const [at, setAt] = (0, import_react9.useState)("");
2626
+ const [busy, setBusy] = (0, import_react9.useState)(false);
2627
+ const [people, setPeople] = (0, import_react9.useState)(null);
2628
+ const [error, setError] = (0, import_react9.useState)(null);
2629
+ async function ask() {
2630
+ setBusy(true);
2631
+ setError(null);
2632
+ const { data, error: failed } = await custom().rpc("who_could_see_on", { p_record_id: recordId, p_at: new Date(at).toISOString() });
2633
+ setBusy(false);
2634
+ const answer = data;
2635
+ if (failed || !answer?.success) {
2636
+ setPeople(null);
2637
+ setError(failed?.message ?? answer?.error ?? "That moment could not be replayed.");
2638
+ return;
2639
+ }
2640
+ setPeople(answer.people ?? []);
2641
+ }
2642
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "mt-3 flex flex-col gap-1.5 border-t border-border pt-3", "data-who-could-see-on": recordId, children: [
2643
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex items-center gap-1 text-xs font-medium", children: [
2644
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.History, { className: "h-3.5 w-3.5" }),
2645
+ "Who could see this on\u2026"
2646
+ ] }),
2647
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1", children: [
2648
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2649
+ import_controls7.DateTimeField,
2650
+ {
2651
+ "aria-label": "Moment to ask about",
2652
+ value: at,
2653
+ onValueChange: (v) => setAt(v ?? ""),
2654
+ className: "min-w-0 flex-1"
2655
+ }
2656
+ ),
2657
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_design_system20.Button, { size: "sm", variant: "outline", className: "h-7 px-2 text-xs", disabled: busy || at === "", onClick: () => void ask(), "data-who-could-see-ask": "", children: [
2658
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react7.Loader2, { className: "mr-1 h-3.5 w-3.5 animate-spin" }) : null,
2659
+ "Ask"
2660
+ ] })
2661
+ ] }),
2662
+ people ? people.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs text-muted-foreground", children: "Nobody could see it then." }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className: "flex flex-col gap-0.5", "data-who-could-see-list": "", children: people.map((p) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { className: "flex items-center justify-between gap-2 text-xs", "data-who-could-see-person": p.id ?? "", children: [
2663
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "min-w-0 truncate", children: [
2664
+ p.name ?? p.email ?? "Someone",
2665
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-muted-foreground", children: [
2666
+ " \xB7 ",
2667
+ p.via
2668
+ ] })
2669
+ ] }),
2670
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex shrink-0 items-center gap-1 text-muted-foreground", children: [
2671
+ p.level ? import_core20.PERMISSION_LEVEL_SHORT_LABELS[p.level] ?? p.level : null,
2672
+ p.has_access_now === false ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rounded border border-border px-1", "data-who-could-see-lost": "", children: "No access now" }) : null
2673
+ ] })
2674
+ ] }, `${p.kind}:${p.id ?? "public"}`)) }) : null,
2675
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", className: "text-xs text-destructive", "data-who-could-see-error": "", children: error }) : null
2676
+ ] });
2677
+ }
2678
+
2679
+ // src/react/sheet/OrgAvailabilityNote.tsx
2680
+ var import_lucide_react8 = require("lucide-react");
2681
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2682
+ var DEFAULT_VERB = {
2683
+ viewer: "view",
2684
+ commenter: "comment on",
2685
+ editor: "edit",
2686
+ admin: "manage"
2687
+ };
2688
+ function OrgAvailabilityNote({
2689
+ permissions,
2690
+ organizationDefault
2691
+ }) {
2692
+ const orgs = permissions.filter(
2693
+ (p) => p.grantedToOrganizationId && !(organizationDefault?.organizationId && p.grantedToOrganizationId === organizationDefault.organizationId)
2694
+ );
2695
+ const defaultLine = organizationDefault ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2696
+ "p",
2697
+ {
2698
+ className: "mt-2 flex items-start gap-1.5 text-xs text-muted-foreground",
2699
+ "data-organization-default": true,
2700
+ children: [
2701
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.Building2, { className: "w-3 h-3 mt-0.5 flex-shrink-0" }),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2703
+ "Everyone in ",
2704
+ organizationDefault.organizationName,
2705
+ " can",
2706
+ " ",
2707
+ DEFAULT_VERB[organizationDefault.level] ?? "open",
2708
+ " this by default."
2709
+ ] })
2710
+ ]
2711
+ }
2712
+ ) : null;
2713
+ if (orgs.length === 0) return defaultLine;
2714
+ const names = orgs.map(
2715
+ (p) => p.grantedToOrganization?.name ?? "an organization"
2716
+ );
2717
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2718
+ defaultLine,
2719
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2720
+ "p",
2721
+ {
2722
+ className: "mt-2 flex items-start gap-1.5 text-xs text-muted-foreground",
2723
+ "data-org-availability": true,
2724
+ children: [
2725
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react8.Building2, { className: "w-3 h-3 mt-0.5 flex-shrink-0" }),
2726
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2727
+ "Also open to everyone in ",
2728
+ names.join(", "),
2729
+ " by its settings."
2730
+ ] })
2731
+ ]
2732
+ }
2733
+ )
2734
+ ] });
2735
+ }
2736
+
2737
+ // src/react/sheet/WhoCanSeeThis.tsx
2738
+ var import_react15 = require("react");
2739
+ var import_lucide_react12 = require("lucide-react");
2740
+ var import_cn5 = require("@ai-matrx/design-system/cn");
2741
+ var import_core32 = require("@ai-matrx/sharing/core");
2742
+
2743
+ // src/react/sheet/RowControls.tsx
2744
+ var import_clipboard4 = require("@ai-matrx/kit/clipboard");
2745
+ var import_react14 = require("react");
2746
+ var import_lucide_react11 = require("lucide-react");
2747
+
2748
+ // src/react/sheet/ui/switch.tsx
2749
+ var import_design_system21 = require("@ai-matrx/design-system");
2750
+ var React6 = __toESM(require("react"), 1);
2751
+ var import_controls8 = require("@ai-matrx/design-system/controls");
2752
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2753
+ var SwitchLegacy = React6.forwardRef(({ size = "sm", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_design_system21.Switch, { ref, size, ...props }));
2754
+ SwitchLegacy.displayName = "SwitchLegacy";
2755
+
2756
+ // src/react/sheet/RowControls.tsx
2757
+ var import_design_system28 = require("@ai-matrx/design-system");
2758
+
2759
+ // src/react/sheet/ui/toggle-group.tsx
2760
+ var import_design_system22 = require("@ai-matrx/design-system");
2761
+
2762
+ // src/react/sheet/RowControls.tsx
2763
+ var import_cn4 = require("@ai-matrx/design-system/cn");
2764
+ var import_core27 = require("@ai-matrx/sharing/core");
2765
+ var import_core28 = require("@ai-matrx/sharing/core");
2766
+ var import_core29 = require("@ai-matrx/sharing/core");
2767
+ var import_core30 = require("@ai-matrx/sharing/core");
2768
+
2769
+ // src/react/sheet/PublicAccessTab.tsx
2770
+ var import_react12 = require("react");
2771
+
2772
+ // src/react/sheet/ui/alert.tsx
2773
+ var import_design_system23 = require("@ai-matrx/design-system");
2774
+
2775
+ // src/react/sheet/PublicAccessTab.tsx
2776
+ var import_lucide_react10 = require("lucide-react");
2777
+
2778
+ // src/react/sheet/ShareLinkPanel.tsx
2779
+ var import_react11 = require("react");
2780
+ var import_design_system26 = require("@ai-matrx/design-system");
2781
+ var import_controls9 = require("@ai-matrx/design-system/controls");
2782
+ var import_lucide_react9 = require("lucide-react");
2783
+ var import_core21 = require("@ai-matrx/sharing/core");
2784
+
2785
+ // src/react/sheet/ui/confirm-dialog.tsx
2786
+ var import_design_system24 = require("@ai-matrx/design-system");
2787
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2788
+ function PortalConfirmDialog(props) {
2789
+ const container = usePortalContainer();
2790
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_design_system24.ConfirmDialog, { ...container ? { portalContainer: container } : {}, ...props });
2791
+ }
2792
+
2793
+ // src/react/sheet/useShare.tsx
2794
+ var import_clipboard3 = require("@ai-matrx/kit/clipboard");
2795
+ var import_react10 = require("react");
2796
+ var import_design_system25 = require("@ai-matrx/design-system");
2797
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2798
+ function useShare() {
2799
+ const { copyText: copyTextKit } = (0, import_clipboard3.useClipboard)({
2800
+ notify: copyNotify
2801
+ });
2802
+ const [copied, setCopied] = (0, import_react10.useState)(false);
2803
+ const [fallbackUrl, setFallbackUrl] = (0, import_react10.useState)(null);
2804
+ const [fallbackTitle, setFallbackTitle] = (0, import_react10.useState)("Copy link");
2805
+ const [fallbackDescription, setFallbackDescription] = (0, import_react10.useState)(
2806
+ "Press Cmd/Ctrl+C to copy, or use the Copy button below."
2807
+ );
2808
+ const copy = (0, import_react10.useCallback)(
2809
+ async (text, fallback) => {
2810
+ const showManualFallback = () => {
2811
+ setFallbackTitle(fallback?.title ?? "Copy link");
2812
+ setFallbackDescription(
2813
+ fallback?.description ?? "Press Cmd/Ctrl+C to copy, or use the Copy button below."
2814
+ );
2815
+ setFallbackUrl(text);
2816
+ return "manual";
2817
+ };
2818
+ if (!await copyTextKit(text)) return showManualFallback();
2819
+ setCopied(true);
2820
+ window.setTimeout(() => setCopied(false), 2e3);
2821
+ return "copied";
2822
+ },
2823
+ []
2824
+ );
2825
+ const share = (0, import_react10.useCallback)(
2826
+ async (options) => {
2827
+ const url = options.url === null ? "" : options.url ?? (typeof window !== "undefined" ? window.location.href : "");
2828
+ const payload = {
2829
+ title: options.title,
2830
+ ...options.text ? { text: options.text } : {},
2831
+ ...url ? { url } : {}
2832
+ };
2833
+ if (typeof navigator !== "undefined" && navigator.share) {
2834
+ try {
2835
+ await navigator.share(payload);
2836
+ return "shared";
2837
+ } catch (error) {
2838
+ if (typeof error === "object" && error !== null && "name" in error && error.name === "AbortError") {
2839
+ return "cancelled";
2840
+ }
2841
+ }
2842
+ }
2843
+ const copyText3 = options.copyText ?? url;
2844
+ return copy(copyText3, {
2845
+ title: options.fallbackTitle,
2846
+ description: options.fallbackDescription
2847
+ });
2848
+ },
2849
+ [copy]
2850
+ );
2851
+ return {
2852
+ share,
2853
+ copy,
2854
+ copied,
2855
+ fallbackDialog: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2856
+ import_design_system25.ClipboardFallbackDialog,
2857
+ {
2858
+ open: fallbackUrl !== null,
2859
+ onOpenChange: (open) => !open && setFallbackUrl(null),
2860
+ url: fallbackUrl ?? "",
2861
+ title: fallbackTitle,
2862
+ description: fallbackDescription
2863
+ }
2864
+ )
2865
+ };
2866
+ }
2867
+
2868
+ // src/react/sheet/ShareLinkPanel.tsx
2869
+ var import_core22 = require("@ai-matrx/sharing/core");
2870
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2871
+ function linkListErrorMessage(error) {
2872
+ return error instanceof Error ? error.message : "We couldn't load this item's share links.";
2873
+ }
2874
+ function ShareLinkPanel({
2875
+ resourceType,
2876
+ resourceId,
2877
+ isOwner,
2878
+ enabled = true
2879
+ }) {
2880
+ const { toast } = useToast();
2881
+ const [links, setLinks] = (0, import_react11.useState)([]);
2882
+ const [loading, setLoading] = (0, import_react11.useState)(true);
2883
+ const [loadedResourceKey, setLoadedResourceKey] = (0, import_react11.useState)(
2884
+ null
2885
+ );
2886
+ const [creating, setCreating] = (0, import_react11.useState)(false);
2887
+ const [copiedId, setCopiedId] = (0, import_react11.useState)(null);
2888
+ const [revokeTarget, setRevokeTarget] = (0, import_react11.useState)(null);
2889
+ const [shareTarget, setShareTarget] = (0, import_react11.useState)(null);
2890
+ const [shareError, setShareError] = (0, import_react11.useState)(null);
2891
+ const [refreshing, setRefreshing] = (0, import_react11.useState)(false);
2892
+ const [handoffBusy, setHandoffBusy] = (0, import_react11.useState)(false);
2893
+ const { share, copy: copyMessage, fallbackDialog } = useShare();
2894
+ const resourceKey = `${resourceType}\0${resourceId}`;
2895
+ const fetchLinks = (0, import_react11.useCallback)(
2896
+ () => (0, import_core21.listShareLinks)(resourceType, resourceId),
2897
+ [resourceType, resourceId]
2898
+ );
2899
+ const refresh = (0, import_react11.useCallback)(async () => {
2900
+ setLoading(true);
2901
+ setShareError(null);
2902
+ try {
2903
+ const freshLinks = await fetchLinks();
2904
+ setLinks(freshLinks);
2905
+ setLoadedResourceKey(resourceKey);
2906
+ return freshLinks;
2907
+ } catch (error) {
2908
+ if (loadedResourceKey !== resourceKey) setLinks([]);
2909
+ setLoadedResourceKey(resourceKey);
2910
+ setShareError(linkListErrorMessage(error));
2911
+ return null;
2912
+ } finally {
2913
+ setLoading(false);
2914
+ }
2915
+ }, [fetchLinks, loadedResourceKey, resourceKey]);
2916
+ (0, import_react11.useEffect)(() => {
2917
+ if (!isOwner) return;
2918
+ let current = true;
2919
+ void fetchLinks().then((freshLinks) => {
2920
+ if (current) {
2921
+ setLinks(freshLinks);
2922
+ setLoadedResourceKey(resourceKey);
2923
+ }
2924
+ }).catch((error) => {
2925
+ if (current) {
2926
+ setLinks([]);
2927
+ setLoadedResourceKey(resourceKey);
2928
+ setShareError(linkListErrorMessage(error));
2929
+ }
2930
+ }).finally(() => {
2931
+ if (current) setLoading(false);
2932
+ });
2933
+ return () => {
2934
+ current = false;
2935
+ };
2936
+ }, [isOwner, fetchLinks, resourceKey]);
2937
+ const activeLinks = links.filter((l) => l.isActive);
2938
+ const isLoading = loading || loadedResourceKey !== resourceKey;
2939
+ const hasLink = !isLoading && activeLinks.length > 0;
2940
+ const copyLink = (0, import_react11.useCallback)(
2941
+ async (sourceLink, alreadyRefreshed) => {
2942
+ const refreshedLinks = alreadyRefreshed ?? await refresh();
2943
+ if (!refreshedLinks) {
2944
+ toast({
2945
+ title: "Couldn't check this link",
2946
+ description: "The link list couldn't be refreshed. Try again before copying.",
2947
+ variant: "destructive"
2948
+ });
2949
+ return;
2950
+ }
2951
+ const currentLink = refreshedLinks.find(
2952
+ (link) => link.id === sourceLink.id
2953
+ );
2954
+ if (!currentLink) {
2955
+ toast({
2956
+ title: "This link is no longer available",
2957
+ description: "Refresh the list and choose an active share link.",
2958
+ variant: "destructive"
2959
+ });
2960
+ return;
2961
+ }
2962
+ const unavailableReason = (0, import_core22.shareLinkUnavailableReason)(currentLink);
2963
+ if (unavailableReason) {
2964
+ toast({
2965
+ title: "This link can't be copied",
2966
+ description: unavailableReason,
2967
+ variant: "destructive"
2968
+ });
2969
+ return;
2970
+ }
2971
+ const outcome = await copyMessage((0, import_core21.shareUrlForLink)(currentLink), {
2972
+ title: "Copy link",
2973
+ description: "Copy this link and send it to the person you want to share with."
2974
+ });
2975
+ if (outcome === "copied") {
2976
+ setCopiedId(currentLink.id);
2977
+ setTimeout(() => setCopiedId(null), 2e3);
2978
+ toast({
2979
+ title: "Link copied",
2980
+ description: "Anyone with it can view \u2014 no sign-in needed."
2981
+ });
2982
+ }
2983
+ },
2984
+ [copyMessage, refresh, toast]
2985
+ );
2986
+ const createLinkOnce = (0, import_react11.useCallback)(async () => {
2987
+ const result = await (0, import_core21.createShareLink)({ resourceType, resourceId });
2988
+ if (result.success && result.url) {
2989
+ const freshLinks = await refresh();
2990
+ const createdLink = result.token && freshLinks ? freshLinks.find((link) => link.token === result.token) : void 0;
2991
+ if (!createdLink || !freshLinks) {
2992
+ toast({
2993
+ title: "Link created but not copied",
2994
+ description: "The current link state couldn't be confirmed. Refresh the list before sharing it.",
2995
+ variant: "destructive"
2996
+ });
2997
+ return;
2998
+ }
2999
+ await copyLink(createdLink, freshLinks);
3000
+ } else {
3001
+ toast({
3002
+ title: "Couldn't create link",
3003
+ description: result.error ?? "Please try again",
3004
+ variant: "destructive"
3005
+ });
3006
+ }
3007
+ }, [resourceType, resourceId, refresh, copyLink, toast]);
3008
+ const creatingRef = (0, import_react11.useRef)(false);
3009
+ const handleCreate = (0, import_react11.useCallback)(async () => {
3010
+ if (creatingRef.current) return;
3011
+ creatingRef.current = true;
3012
+ setCreating(true);
3013
+ try {
3014
+ await createLinkOnce();
3015
+ } finally {
3016
+ creatingRef.current = false;
3017
+ setCreating(false);
3018
+ }
3019
+ }, [createLinkOnce]);
3020
+ const handleRevoke = (0, import_react11.useCallback)(async () => {
3021
+ if (!revokeTarget) return;
3022
+ const result = await (0, import_core21.revokeShareLink)(revokeTarget.id);
3023
+ setRevokeTarget(null);
3024
+ if (result.success) {
3025
+ await refresh();
3026
+ toast({
3027
+ title: "Link turned off",
3028
+ description: "It can no longer be opened."
3029
+ });
3030
+ } else {
3031
+ toast({
3032
+ title: "Couldn't revoke",
3033
+ description: result.error,
3034
+ variant: "destructive"
3035
+ });
3036
+ }
3037
+ }, [revokeTarget, refresh, toast]);
3038
+ const prepareTextShare = (0, import_react11.useCallback)(
3039
+ async (linkId) => {
3040
+ setRefreshing(true);
3041
+ const freshLinks = await refresh();
3042
+ setRefreshing(false);
3043
+ if (!freshLinks) {
3044
+ toast({
3045
+ title: "Couldn't check this link",
3046
+ description: "The link list couldn't be refreshed. Try again before sharing.",
3047
+ variant: "destructive"
3048
+ });
3049
+ return;
3050
+ }
3051
+ const freshLink = freshLinks.find((link) => link.id === linkId);
3052
+ if (!freshLink) {
3053
+ toast({
3054
+ title: "This link is no longer available",
3055
+ description: "Refresh the list and choose an active share link.",
3056
+ variant: "destructive"
3057
+ });
3058
+ return;
3059
+ }
3060
+ const unavailableReason = (0, import_core22.shareLinkUnavailableReason)(freshLink);
3061
+ if (unavailableReason) {
3062
+ toast({
3063
+ title: "This link can't be shared",
3064
+ description: unavailableReason,
3065
+ variant: "destructive"
3066
+ });
3067
+ return;
3068
+ }
3069
+ setShareTarget(freshLink);
3070
+ },
3071
+ [refresh, toast]
3072
+ );
3073
+ const closeShareReview = (0, import_react11.useCallback)(() => {
3074
+ if (!handoffBusy) setShareTarget(null);
3075
+ }, [handoffBusy]);
3076
+ const handleNativeHandoff = (0, import_react11.useCallback)(async () => {
3077
+ if (!shareTarget) return;
3078
+ setHandoffBusy(true);
3079
+ try {
3080
+ const freshLinks = await refresh();
3081
+ if (!freshLinks) {
3082
+ toast({
3083
+ title: "Couldn't check this link",
3084
+ description: "The link list couldn't be refreshed. Try again before sharing.",
3085
+ variant: "destructive"
3086
+ });
3087
+ return;
3088
+ }
3089
+ const currentLink = freshLinks.find((link) => link.id === shareTarget.id);
3090
+ if (!currentLink) {
3091
+ setShareTarget(null);
3092
+ toast({
3093
+ title: "This link is no longer available",
3094
+ description: "It may have been turned off. Choose an active link to continue.",
3095
+ variant: "destructive"
3096
+ });
3097
+ return;
3098
+ }
3099
+ const handoffReview = (0, import_core22.evaluateShareLinkHandoff)(shareTarget, currentLink);
3100
+ if (handoffReview.status === "unavailable") {
3101
+ setShareTarget(null);
3102
+ toast({
3103
+ title: "This link can't be shared",
3104
+ description: handoffReview.reason,
3105
+ variant: "destructive"
3106
+ });
3107
+ return;
3108
+ }
3109
+ if (handoffReview.status === "changed") {
3110
+ setShareTarget(currentLink);
3111
+ toast({
3112
+ title: "Link access changed",
3113
+ description: "Review the updated access, expiry, and view count before sharing.",
3114
+ variant: "destructive"
3115
+ });
3116
+ return;
3117
+ }
3118
+ const message = (0, import_core22.createSafeShareMessage)((0, import_core21.shareUrlForLink)(currentLink));
3119
+ const outcome = await share({
3120
+ title: "AI Matrx share",
3121
+ text: message,
3122
+ url: null,
3123
+ copyText: message,
3124
+ fallbackTitle: "Copy message",
3125
+ fallbackDescription: "Copy the reviewed message below and paste it into your messaging app."
3126
+ });
3127
+ if (outcome === "cancelled") return;
3128
+ setShareTarget(null);
3129
+ if (outcome === "shared") {
3130
+ toast({
3131
+ title: "Share options opened",
3132
+ description: "AI Matrx didn't send the message. Complete it in your share app."
3133
+ });
3134
+ } else if (outcome === "copied") {
3135
+ toast({
3136
+ title: "Message copied",
3137
+ description: "Paste it into a message. AI Matrx didn't send it."
3138
+ });
3139
+ }
3140
+ } finally {
3141
+ setHandoffBusy(false);
3142
+ }
3143
+ }, [shareTarget, refresh, share, toast]);
3144
+ const handleCopyReviewedMessage = (0, import_react11.useCallback)(async () => {
3145
+ if (!shareTarget) return;
3146
+ setHandoffBusy(true);
3147
+ try {
3148
+ const freshLinks = await refresh();
3149
+ if (!freshLinks) {
3150
+ toast({
3151
+ title: "Couldn't check this link",
3152
+ description: "The link list couldn't be refreshed. Try again before copying.",
3153
+ variant: "destructive"
3154
+ });
3155
+ return;
3156
+ }
3157
+ const currentLink = freshLinks.find((link) => link.id === shareTarget.id);
3158
+ if (!currentLink) {
3159
+ setShareTarget(null);
3160
+ toast({
3161
+ title: "This link is no longer available",
3162
+ description: "It may have been turned off. Choose an active link to continue.",
3163
+ variant: "destructive"
3164
+ });
3165
+ return;
3166
+ }
3167
+ const handoffReview = (0, import_core22.evaluateShareLinkHandoff)(shareTarget, currentLink);
3168
+ if (handoffReview.status === "unavailable") {
3169
+ setShareTarget(null);
3170
+ toast({
3171
+ title: "This link can't be shared",
3172
+ description: handoffReview.reason,
3173
+ variant: "destructive"
3174
+ });
3175
+ return;
3176
+ }
3177
+ if (handoffReview.status === "changed") {
3178
+ setShareTarget(currentLink);
3179
+ toast({
3180
+ title: "Link access changed",
3181
+ description: "Review the updated access, expiry, and view count before copying.",
3182
+ variant: "destructive"
3183
+ });
3184
+ return;
3185
+ }
3186
+ const message = (0, import_core22.createSafeShareMessage)((0, import_core21.shareUrlForLink)(currentLink));
3187
+ const outcome = await copyMessage(message, {
3188
+ title: "Copy message",
3189
+ description: "Copy the reviewed message below and paste it into your messaging app."
3190
+ });
3191
+ setShareTarget(null);
3192
+ if (outcome === "copied") {
3193
+ toast({
3194
+ title: "Message copied",
3195
+ description: "Paste it into a message. AI Matrx didn't send it."
3196
+ });
3197
+ }
3198
+ } finally {
3199
+ setHandoffBusy(false);
3200
+ }
3201
+ }, [shareTarget, refresh, copyMessage, toast]);
3202
+ if (!isOwner) return null;
3203
+ if (!enabled) {
3204
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-start gap-2 p-3 bg-muted/30 rounded-lg border", children: [
3205
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Link2, { className: "w-4 h-4 text-muted-foreground flex-shrink-0 mt-0.5" }),
3206
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "min-w-0", children: [
3207
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-medium", children: "No-login links aren\u2019t on for this item type" }),
3208
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-muted-foreground", children: "Share it with specific people or an organization instead \u2014 they open it signed in." })
3209
+ ] })
3210
+ ] });
3211
+ }
3212
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-2.5 p-3 bg-muted/30 rounded-lg border", children: [
3213
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
3214
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2 min-w-0", children: [
3215
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Link2, { className: "w-4 h-4 text-primary flex-shrink-0" }),
3216
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "min-w-0", children: [
3217
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "text-sm font-medium", children: "Anyone with the link" }),
3218
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-muted-foreground", children: hasLink ? `Link is on \xB7 ${activeLinks.length} active` : "Create a link that opens with no sign-in required" })
3219
+ ] })
3220
+ ] }),
3221
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3222
+ import_design_system26.Button,
3223
+ {
3224
+ size: "sm",
3225
+ onClick: handleCreate,
3226
+ disabled: creating,
3227
+ className: "flex-shrink-0",
3228
+ children: [
3229
+ creating ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Loader2, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Plus, { className: "w-4 h-4" }),
3230
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ml-1 hidden sm:inline", children: hasLink ? "New link" : "Create link" })
3231
+ ]
3232
+ }
3233
+ )
3234
+ ] }),
3235
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-center py-3 text-xs text-muted-foreground", children: [
3236
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Loader2, { className: "w-3.5 h-3.5 animate-spin mr-2" }),
3237
+ "Loading links\u2026"
3238
+ ] }) : activeLinks.length === 0 && !shareError ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-muted-foreground py-1", children: "No share links yet. Create one to share this with anyone." }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-1.5", children: activeLinks.map((link) => {
3239
+ const unavailableReason = (0, import_core22.shareLinkUnavailableReason)(link);
3240
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3241
+ "div",
3242
+ {
3243
+ className: "flex items-center gap-1.5 rounded-md border bg-background p-1.5",
3244
+ children: [
3245
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3246
+ import_controls9.Input,
3247
+ {
3248
+ mono: true,
3249
+ readOnly: true,
3250
+ value: (0, import_core21.shareUrlForLink)(link),
3251
+ className: "min-w-0 flex-1",
3252
+ onFocus: (e) => e.currentTarget.select()
3253
+ }
3254
+ ),
3255
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3256
+ "span",
3257
+ {
3258
+ className: "flex items-center gap-1 text-[10px] text-muted-foreground px-1",
3259
+ title: `${link.useCount} view${link.useCount === 1 ? "" : "s"}`,
3260
+ children: [
3261
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Eye, { className: "w-3 h-3" }),
3262
+ link.useCount
3263
+ ]
3264
+ }
3265
+ ),
3266
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3267
+ import_design_system26.Button,
3268
+ {
3269
+ variant: "ghost",
3270
+ size: "icon",
3271
+ className: "h-11 w-11 flex-shrink-0 sm:h-8 sm:w-8",
3272
+ onClick: () => copyLink(link),
3273
+ disabled: !!unavailableReason,
3274
+ title: unavailableReason ?? "Copy link",
3275
+ "aria-label": "Copy share link",
3276
+ children: copiedId === link.id ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Check, { className: "w-4 h-4 text-green-500" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Copy, { className: "w-4 h-4" })
3277
+ }
3278
+ ),
3279
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3280
+ import_design_system26.Button,
3281
+ {
3282
+ variant: "ghost",
3283
+ size: "icon",
3284
+ className: "h-11 w-11 flex-shrink-0 sm:h-8 sm:w-8",
3285
+ onClick: () => prepareTextShare(link.id),
3286
+ disabled: !!unavailableReason || refreshing,
3287
+ title: unavailableReason ?? "Review before sharing by text",
3288
+ "aria-label": "Share by text",
3289
+ children: refreshing ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Loader2, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.MessageSquareText, { className: "w-4 h-4" })
3290
+ }
3291
+ ),
3292
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3293
+ import_design_system26.Button,
3294
+ {
3295
+ variant: "ghost",
3296
+ size: "icon",
3297
+ className: "h-11 w-11 flex-shrink-0 text-destructive sm:h-8 sm:w-8",
3298
+ onClick: () => setRevokeTarget(link),
3299
+ title: "Turn off link",
3300
+ "aria-label": "Turn off share link",
3301
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Trash2, { className: "w-4 h-4" })
3302
+ }
3303
+ )
3304
+ ]
3305
+ },
3306
+ link.id
3307
+ );
3308
+ }) }),
3309
+ shareError ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3310
+ "div",
3311
+ {
3312
+ role: "alert",
3313
+ className: "flex items-center justify-between gap-2 text-xs text-destructive",
3314
+ children: [
3315
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: shareError }),
3316
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3317
+ import_design_system26.Button,
3318
+ {
3319
+ type: "button",
3320
+ variant: "outline",
3321
+ size: "sm",
3322
+ disabled: isLoading,
3323
+ onClick: () => refresh(),
3324
+ children: [
3325
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.RefreshCw, { className: "mr-1 h-3.5 w-3.5" }),
3326
+ "Refresh"
3327
+ ]
3328
+ }
3329
+ ),
3330
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ErrorAlchemyMenu, { className: "ml-auto" })
3331
+ ]
3332
+ }
3333
+ ) : null,
3334
+ refreshing && !loading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "sr-only", role: "status", children: "Checking current link access" }) : null,
3335
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3336
+ PortalConfirmDialog,
3337
+ {
3338
+ open: shareTarget !== null,
3339
+ onOpenChange: (open) => !open && closeShareReview(),
3340
+ title: "Review message before sharing",
3341
+ description: "Anyone with this link can open it without signing in. AI Matrx will prepare the message; it won't send it.",
3342
+ content: shareTarget ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-3 text-sm", children: [
3343
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("dl", { className: "space-y-1.5 text-xs", children: [
3344
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex justify-between gap-3", children: [
3345
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { className: "text-muted-foreground", children: "Access" }),
3346
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { className: "text-right", children: (0, import_core22.describeShareLinkAccess)(shareTarget.permissionLevel) })
3347
+ ] }),
3348
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex justify-between gap-3", children: [
3349
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { className: "text-muted-foreground", children: "Expiry" }),
3350
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { className: "text-right", children: (0, import_core22.describeShareLinkExpiry)(shareTarget.expiresAt) })
3351
+ ] }),
3352
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex justify-between gap-3", children: [
3353
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { className: "text-muted-foreground", children: "Views" }),
3354
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { className: "text-right", children: shareTarget.maxUses === null ? `${shareTarget.useCount} used \xB7 no limit` : `${shareTarget.useCount} of ${shareTarget.maxUses} used` })
3355
+ ] })
3356
+ ] }),
3357
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "space-y-1", children: [
3358
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs font-medium", children: "Message preview" }),
3359
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "rounded-md border bg-muted/30 p-3 break-words", children: (0, import_core22.createSafeShareMessage)((0, import_core21.shareUrlForLink)(shareTarget)) })
3360
+ ] }),
3361
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3362
+ import_design_system26.Button,
3363
+ {
3364
+ type: "button",
3365
+ variant: "outline",
3366
+ className: "min-h-11 w-full sm:min-h-0",
3367
+ disabled: handoffBusy,
3368
+ onClick: handleCopyReviewedMessage,
3369
+ children: [
3370
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react9.Copy, { className: "mr-2 h-4 w-4" }),
3371
+ "Copy message"
3372
+ ]
3373
+ }
3374
+ ),
3375
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-muted-foreground", children: "You can turn this link off from the share panel at any time." })
3376
+ ] }) : null,
3377
+ confirmLabel: typeof navigator !== "undefined" && typeof navigator.share === "function" ? "Open share options" : "Copy message",
3378
+ cancelLabel: "Not now",
3379
+ contentClassName: "w-[calc(100%-2rem)] sm:!max-w-[34rem]",
3380
+ busy: handoffBusy,
3381
+ onConfirm: handleNativeHandoff
3382
+ }
3383
+ ),
3384
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3385
+ PortalConfirmDialog,
3386
+ {
3387
+ open: !!revokeTarget,
3388
+ onOpenChange: (o) => !o && setRevokeTarget(null),
3389
+ title: "Turn off this link?",
3390
+ description: "Anyone currently using it will lose access. This can't be undone.",
3391
+ confirmLabel: "Turn off link",
3392
+ variant: "destructive",
3393
+ onConfirm: handleRevoke
3394
+ }
3395
+ ),
3396
+ fallbackDialog
3397
+ ] });
3398
+ }
3399
+
3400
+ // src/react/sheet/PublicAccessTab.tsx
3401
+ var import_design_system27 = require("@ai-matrx/design-system");
3402
+ var import_core23 = require("@ai-matrx/sharing/core");
3403
+ var import_core24 = require("@ai-matrx/sharing/core");
3404
+ var import_core25 = require("@ai-matrx/sharing/core");
3405
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3406
+ function anyoneReachWords(typeLabel, reach) {
3407
+ if (reach.publicPage) return `Anyone, signed in or not, opens this ${typeLabel} at its address as a viewer.`;
3408
+ return `Everyone signed in to AI Matrx can find and view this ${typeLabel}.` + (reach.noLoginLink ? " People without an account need a no-login link." : " People without an account cannot open it.");
3409
+ }
3410
+ function PublicAccessTab({
3411
+ isOwner,
3412
+ resourceType,
3413
+ resourceId,
3414
+ resourceName,
3415
+ copy
3416
+ }) {
3417
+ const [linkShareable, setLinkShareable] = (0, import_react12.useState)(false);
3418
+ const [loading, setLoading] = (0, import_react12.useState)(true);
3419
+ const [capabilitiesError, setCapabilitiesError] = (0, import_react12.useState)(null);
3420
+ (0, import_react12.useEffect)(() => {
3421
+ let active = true;
3422
+ setLoading(true);
3423
+ setCapabilitiesError(null);
3424
+ (0, import_core24.getShareCapabilities)(resourceType).then((c) => {
3425
+ if (active) setLinkShareable(c.isLinkShareable);
3426
+ }).catch((error) => {
3427
+ if (active) setCapabilitiesError((0, import_core23.extractErrorMessage)(error));
3428
+ }).finally(() => {
3429
+ if (active) setLoading(false);
3430
+ });
3431
+ return () => {
3432
+ active = false;
3433
+ };
3434
+ }, [resourceType]);
3435
+ const location = (0, import_core25.sharingLocation)(copy?.surface ?? "Anyone link");
3436
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system27.Skeleton, { className: "h-20 w-full" });
3437
+ if (capabilitiesError) {
3438
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system23.Alert, { variant: "destructive", className: "group", children: [
3439
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react10.AlertTriangle, { className: "h-4 w-4" }),
3440
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system23.AlertDescription, { className: "flex items-start gap-2", children: [
3441
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "flex-1", children: capabilitiesError }),
3442
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "shrink-0 opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3443
+ CopyButtons,
3444
+ {
3445
+ size: "xs",
3446
+ label: "Anyone link error",
3447
+ human: () => [
3448
+ "The Anyone link controls could not load.",
3449
+ capabilitiesError,
3450
+ `Resource: ${resourceType}:${resourceId} (${resourceName})`
3451
+ ].join("\n"),
3452
+ json: () => ({
3453
+ error: capabilitiesError,
3454
+ resource_type: resourceType,
3455
+ resource_id: resourceId
3456
+ }),
3457
+ agent: () => ({
3458
+ kind: "public-access-error",
3459
+ location,
3460
+ description: "The share-capabilities lookup failed, so the Anyone link tab renders no controls. This is the error on screen, verbatim.",
3461
+ data: {
3462
+ rendered_error: capabilitiesError,
3463
+ resource_type: resourceType,
3464
+ resource_id: resourceId,
3465
+ resource_name: resourceName,
3466
+ kpis: copy?.kpis ?? null
3467
+ },
3468
+ attributes: { resource_type: resourceType, resource_id: resourceId, state: "error" }
3469
+ })
3470
+ }
3471
+ ) })
3472
+ ] })
3473
+ ] });
3474
+ }
3475
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "space-y-3", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3476
+ ShareLinkPanel,
3477
+ {
3478
+ resourceType,
3479
+ resourceId,
3480
+ isOwner,
3481
+ enabled: linkShareable
3482
+ }
3483
+ ) });
3484
+ }
3485
+
3486
+ // src/react/sheet/RowControls.tsx
3487
+ var import_core31 = require("@ai-matrx/sharing/core");
3488
+
3489
+ // src/react/sheet/SearchEngineIndexedSwitch.tsx
3490
+ var import_react13 = require("react");
3491
+ var import_cn3 = require("@ai-matrx/design-system/cn");
3492
+ var import_core26 = require("@ai-matrx/sharing/core");
3493
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3494
+ function SearchEngineIndexedSwitch({
3495
+ resourceType,
3496
+ resourceId,
3497
+ publishedHint,
3498
+ className
3499
+ }) {
3500
+ const [state2, setState] = (0, import_react13.useState)(null);
3501
+ const [busy, setBusy] = (0, import_react13.useState)(false);
3502
+ const { toast } = useToast();
3503
+ const load = (0, import_react13.useCallback)(async () => {
3504
+ const res = await (0, import_core26.getSearchEngineIndexedState)(resourceType, resourceId);
3505
+ if (res.error) {
3506
+ console.warn(`[search-engine-indexed] state for ${resourceType}/${resourceId}: ${res.error}`);
3507
+ setState(null);
3508
+ return;
3509
+ }
3510
+ setState(res.state);
3511
+ }, [resourceType, resourceId]);
3512
+ (0, import_react13.useEffect)(() => {
3513
+ void load();
3514
+ }, [load, publishedHint]);
3515
+ if (!state2 || !state2.enrolled || !state2.published_to_web) return null;
3516
+ const onChange = async (next) => {
3517
+ setBusy(true);
3518
+ const res = await (0, import_core26.setSearchEngineIndexed)(resourceType, resourceId, next);
3519
+ setBusy(false);
3520
+ if (res.error || !res.state) {
3521
+ toast({
3522
+ title: "Couldn't change search engine indexing",
3523
+ description: res.error ?? "Please try again",
3524
+ variant: "destructive"
3525
+ });
3526
+ return;
3527
+ }
3528
+ setState(res.state);
3529
+ toast({
3530
+ title: next ? "Search engines may list this page" : "Search engines are told not to list this page",
3531
+ description: "Anyone with the address can still open it."
3532
+ });
3533
+ };
3534
+ const followsDefault = state2.value === null;
3535
+ const id = `indexed-${resourceType}-${resourceId}`;
3536
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3537
+ "span",
3538
+ {
3539
+ className: (0, import_cn3.cn)("inline-flex shrink-0 items-center gap-1.5", className),
3540
+ title: followsDefault ? `Following the default for this type (${state2.type_default ? "indexed" : "not indexed"}).` : "Set for this item.",
3541
+ children: [
3542
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("label", { htmlFor: id, className: "cursor-pointer text-xs text-muted-foreground", children: "Indexed by search engines" }),
3543
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3544
+ import_controls8.Switch,
3545
+ {
3546
+ id,
3547
+ checked: state2.effective,
3548
+ onCheckedChange: onChange,
3549
+ disabled: busy || !state2.can_change,
3550
+ "aria-label": "Indexed by search engines"
3551
+ }
3552
+ )
3553
+ ]
3554
+ }
3555
+ );
3556
+ }
3557
+
3558
+ // src/react/sheet/RowControls.tsx
3559
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3560
+ var SAYS_SLOT_KEYS = [...import_core31.SHOWN_TO_ORDER, "default"];
3561
+ function RowControls({
3562
+ resourceType,
3563
+ resourceId,
3564
+ canChange,
3565
+ shownTo,
3566
+ isPublic,
3567
+ childRecord = false,
3568
+ publishable = true,
3569
+ onSetShownTo,
3570
+ onPublish,
3571
+ onStopPublishing
3572
+ }) {
3573
+ const { copyText: copyText3 } = (0, import_clipboard4.useClipboard)({
3574
+ notify: copyNotify
3575
+ });
3576
+ const { toast } = useToast();
3577
+ const [caps, setCaps] = (0, import_react14.useState)(null);
3578
+ const [capsError, setCapsError] = (0, import_react14.useState)(null);
3579
+ const [busy, setBusy] = (0, import_react14.useState)(null);
3580
+ const [copied, setCopied] = (0, import_react14.useState)(false);
3581
+ (0, import_react14.useEffect)(() => {
3582
+ let live = true;
3583
+ setCapsError(null);
3584
+ (0, import_core28.getShareCapabilities)(resourceType).then((c) => live && setCaps(c)).catch((e) => live && setCapsError((0, import_core27.extractErrorMessage)(e)));
3585
+ return () => {
3586
+ live = false;
3587
+ };
3588
+ }, [resourceType]);
3589
+ const publicUrl = (0, import_core29.publicResourceUrl)(resourceType, resourceId);
3590
+ const copyAddress = (0, import_react14.useCallback)(async () => {
3591
+ if (!publicUrl) return;
3592
+ if (!await copyText3(publicUrl, void 0, "Couldn't copy the web address")) return;
3593
+ setCopied(true);
3594
+ setTimeout(() => setCopied(false), 2e3);
3595
+ }, [publicUrl, toast]);
3596
+ if (capsError) {
3597
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "rounded-lg border border-destructive/20 bg-destructive/10 p-3 text-xs text-destructive-ink", children: [
3598
+ "Couldn't read this item's sharing options: ",
3599
+ capsError,
3600
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ErrorAlchemyMenu, { error: capsError })
3601
+ ] });
3602
+ }
3603
+ if (childRecord) return null;
3604
+ if (!caps) {
3605
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("section", { "aria-hidden": "true", className: "invisible space-y-2 rounded-lg border p-3", "data-row-controls-reserved": "", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex h-8 items-center" }) });
3606
+ }
3607
+ const showShownTo = caps.shownToOffered && shownTo !== void 0;
3608
+ const showPublish = publishable && caps.publishLane !== null;
3609
+ if (!showShownTo && !showPublish) return null;
3610
+ const shownChoices = import_core31.SHOWN_TO_ORDER.filter(
3611
+ (v) => v !== "everyone_on_ai_matrx" || isPublic || shownTo === v
3612
+ );
3613
+ const publishSays = caps.publishLane === "card" ? "Only its card \u2014 the name, description and inputs \u2014 opens for anyone. What is inside stays in its organization." : (
3614
+ // Never promise a signed-out read the type cannot deliver (no page of its own).
3615
+ anyoneReachWords(
3616
+ (0, import_core30.getShareableResource)(resourceType)?.displayLabel?.toLowerCase() ?? "item",
3617
+ { publicPage: Boolean(publicUrl), noLoginLink: caps.isLinkShareable }
3618
+ )
3619
+ );
3620
+ const chooseShownTo = async (next) => {
3621
+ if (!canChange || next === shownTo || busy) return;
3622
+ setBusy("shown");
3623
+ try {
3624
+ const result = await onSetShownTo(next);
3625
+ if (result.success === false) {
3626
+ toast({
3627
+ title: `Couldn't change "Shown to"`,
3628
+ description: result.error || "Please try again",
3629
+ variant: "destructive"
3630
+ });
3631
+ }
3632
+ } finally {
3633
+ setBusy(null);
3634
+ }
3635
+ };
3636
+ const togglePublish = async (on) => {
3637
+ if (!canChange || busy) return;
3638
+ setBusy("publish");
3639
+ try {
3640
+ const result = on ? await onPublish() : await onStopPublishing();
3641
+ toast(
3642
+ result.success === false ? {
3643
+ title: on ? "Couldn't publish it to the web" : "Couldn't stop publishing it to the web",
3644
+ description: result.error || "Please try again",
3645
+ variant: "destructive"
3646
+ } : {
3647
+ title: on ? "Published to the web" : "No longer published to the web",
3648
+ description: on ? publishSays : "People outside its organization can no longer open it, unless it is shared with them."
3649
+ }
3650
+ );
3651
+ } finally {
3652
+ setBusy(null);
3653
+ }
3654
+ };
3655
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3656
+ "section",
3657
+ {
3658
+ className: "space-y-2 rounded-lg border bg-muted/30 p-3",
3659
+ "data-row-controls": true,
3660
+ "data-published": isPublic ? "true" : "false",
3661
+ children: [
3662
+ showShownTo && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2", "data-shown-to": shownTo ?? "default", children: [
3663
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs font-medium", children: "Shown to" }),
3664
+ canChange ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3665
+ import_design_system22.ToggleGroup,
3666
+ {
3667
+ type: "single",
3668
+ size: "sm",
3669
+ variant: "outline",
3670
+ value: shownTo ?? "",
3671
+ onValueChange: (v) => v && void chooseShownTo(v),
3672
+ "aria-label": "Shown to",
3673
+ className: "flex-wrap justify-end",
3674
+ children: shownChoices.map((v) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3675
+ import_design_system22.ToggleGroupItem,
3676
+ {
3677
+ value: v,
3678
+ "aria-label": import_core31.SHOWN_TO_WORDS[v].label,
3679
+ title: import_core31.SHOWN_TO_WORDS[v].says,
3680
+ className: "h-7 px-2 text-xs data-[state=on]:border-primary data-[state=on]:bg-primary/10 data-[state=on]:text-primary-ink",
3681
+ children: import_core31.SHOWN_TO_WORDS[v].label
3682
+ },
3683
+ v
3684
+ ))
3685
+ }
3686
+ ) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs text-muted-foreground", children: shownTo ? import_core31.SHOWN_TO_WORDS[shownTo].label : "The default for this type" })
3687
+ ] }),
3688
+ showShownTo && // ZERO LAYOUT SHIFT (Arman 2026-10-07): the sentence under "Shown to" changes with every
3689
+ // choice and each one wraps to a different number of lines on a phone. The slot is a
3690
+ // one-cell grid holding EVERY sentence, only the current one visible, so its height is the
3691
+ // tallest one's at any width and nothing below it ever moves. The spinner's place is held
3692
+ // too (invisible, never removed).
3693
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "grid text-xs text-muted-foreground", "data-shown-to-says-slot": "", children: SAYS_SLOT_KEYS.map((v) => {
3694
+ const current = (shownTo ?? "default") === v;
3695
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3696
+ "p",
3697
+ {
3698
+ className: (0, import_cn4.cn)("col-start-1 row-start-1", !current && "invisible"),
3699
+ "aria-hidden": !current,
3700
+ "data-shown-to-says": v,
3701
+ children: [
3702
+ v === "default" ? "Not set \u2014 the default for this type applies." : import_core31.SHOWN_TO_WORDS[v].says,
3703
+ " ",
3704
+ "A list setting, never who may open it.",
3705
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3706
+ import_lucide_react11.Loader2,
3707
+ {
3708
+ className: (0, import_cn4.cn)(
3709
+ "ml-1 inline h-3 w-3 animate-spin",
3710
+ !(current && busy === "shown") && "invisible"
3711
+ )
3712
+ }
3713
+ )
3714
+ ]
3715
+ },
3716
+ v
3717
+ );
3718
+ }) }),
3719
+ showPublish && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: (0, import_cn4.cn)("space-y-1.5", showShownTo && "border-t pt-2"), children: [
3720
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
3721
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs font-medium", children: "Published to the web" }),
3722
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex items-center gap-3", children: [
3723
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3724
+ SearchEngineIndexedSwitch,
3725
+ {
3726
+ resourceType,
3727
+ resourceId,
3728
+ publishedHint: isPublic
3729
+ }
3730
+ ),
3731
+ busy === "publish" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react11.Loader2, { className: "h-3.5 w-3.5 animate-spin text-muted-foreground" }),
3732
+ canChange ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3733
+ import_controls8.Switch,
3734
+ {
3735
+ checked: isPublic,
3736
+ onCheckedChange: (on) => void togglePublish(on),
3737
+ disabled: busy === "publish",
3738
+ "aria-label": "Published to the web"
3739
+ }
3740
+ ) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs text-muted-foreground", children: isPublic ? "On" : "Off" })
3741
+ ] })
3742
+ ] }),
3743
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-xs text-muted-foreground", children: isPublic ? publishSays : `When on: ${publishSays}` }),
3744
+ isPublic && publicUrl && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-1.5 rounded-md border bg-background p-1.5", children: [
3745
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "min-w-0 flex-1 truncate px-1 font-mono text-xs", children: publicUrl }),
3746
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3747
+ import_design_system28.Button,
3748
+ {
3749
+ variant: "ghost",
3750
+ size: "icon",
3751
+ className: "h-7 w-7 flex-shrink-0",
3752
+ onClick: () => void copyAddress(),
3753
+ title: "Copy web address",
3754
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react11.Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react11.Copy, { className: "h-3.5 w-3.5" })
3755
+ }
3756
+ )
3757
+ ] })
3758
+ ] })
3759
+ ]
3760
+ }
3761
+ );
3762
+ }
3763
+
3764
+ // src/react/sheet/WhoCanSeeThis.tsx
3765
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3766
+ var LEVEL_WORD = {
3767
+ viewer: "Viewer",
3768
+ commenter: "Commenter",
3769
+ editor: "Editor",
3770
+ admin: "Admin"
3771
+ };
3772
+ var ONLY_ME_HINT = "Hidden from members' lists. Members with the link can still open it.";
3773
+ function WhoCanSeeThis({
3774
+ whoCanSee,
3775
+ canChange,
3776
+ onChoose,
3777
+ row
3778
+ }) {
3779
+ const { orgs } = useNavTree();
3780
+ const [pending, setPending] = (0, import_react15.useState)(null);
3781
+ const [said, setSaid] = (0, import_react15.useState)(null);
3782
+ if (!whoCanSee) return row ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RowControls, { ...row, canChange }) : null;
3783
+ const orgName = whoCanSee.organizationName ?? (whoCanSee.organizationId ? orgs.find((o) => o.id === whoCanSee.organizationId)?.name : void 0) ?? "this organization";
3784
+ const level = whoCanSee.memberDefaultLevel ? LEVEL_WORD[whoCanSee.memberDefaultLevel] ?? whoCanSee.memberDefaultLevel : null;
3785
+ const options = [
3786
+ {
3787
+ choice: "mine",
3788
+ label: import_core32.SHOWN_TO_WORDS.only_me.label,
3789
+ says: import_core32.SHOWN_TO_WORDS.only_me.says,
3790
+ hint: ONLY_ME_HINT,
3791
+ icon: import_lucide_react12.EyeOff
3792
+ },
3793
+ {
3794
+ choice: "organization",
3795
+ label: `Everyone in ${orgName}`,
3796
+ says: level ? `Every member, as ${level}. This is the organization's default.` : "Every member. This is the organization's default.",
3797
+ icon: import_lucide_react12.Building2
3798
+ }
3799
+ ];
3800
+ if (whoCanSee.worldOffered || whoCanSee.choice === "world") {
3801
+ options.push({
3802
+ choice: "world",
3803
+ label: "Anyone with the link",
3804
+ says: "Out in the world, findable by the link and nothing else.",
3805
+ icon: import_lucide_react12.Globe
3806
+ });
3807
+ }
3808
+ const current = options.find((o) => o.choice === whoCanSee.choice) ?? options[options.length - 1];
3809
+ const apply = async (choice) => {
3810
+ setPending(choice);
3811
+ setSaid(null);
3812
+ try {
3813
+ const result = await onChoose(choice);
3814
+ setSaid(
3815
+ result.success ? { ok: true, text: result.message ?? "Saved." } : { ok: false, text: result.error ?? "That could not be changed." }
3816
+ );
3817
+ } finally {
3818
+ setPending(null);
3819
+ }
3820
+ };
3821
+ const pick = (choice) => {
3822
+ if (choice === whoCanSee.choice || pending) return;
3823
+ void apply(choice);
3824
+ };
3825
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3826
+ "section",
3827
+ {
3828
+ className: "space-y-1.5 p-3 bg-muted/30 rounded-lg border",
3829
+ "data-who-can-see": true,
3830
+ "data-lane": whoCanSee.choice,
3831
+ children: [
3832
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-xs font-medium", children: "Who can see this" }),
3833
+ canChange ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3834
+ "div",
3835
+ {
3836
+ role: "radiogroup",
3837
+ "aria-label": "Who can see this",
3838
+ className: "grid grid-cols-1 gap-1.5 sm:grid-flow-col sm:auto-cols-fr",
3839
+ children: options.map((o) => {
3840
+ const Icon = o.icon;
3841
+ const selected = o.choice === whoCanSee.choice;
3842
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative min-w-0", children: [
3843
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3844
+ "button",
3845
+ {
3846
+ type: "button",
3847
+ role: "radio",
3848
+ "aria-checked": selected,
3849
+ "data-lane-choice": o.choice,
3850
+ onClick: () => pick(o.choice),
3851
+ className: (0, import_cn5.cn)(
3852
+ "flex w-full min-w-0 items-start gap-2 rounded-md border p-2 text-left transition-colors",
3853
+ o.hint && "pr-7",
3854
+ selected ? "border-primary bg-background" : "border-transparent hover:bg-background/60"
3855
+ ),
3856
+ children: [
3857
+ pending === o.choice ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react12.Loader2, { className: "mt-0.5 h-4 w-4 flex-shrink-0 animate-spin text-primary" }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3858
+ Icon,
3859
+ {
3860
+ className: (0, import_cn5.cn)(
3861
+ "mt-0.5 h-4 w-4 flex-shrink-0",
3862
+ selected ? "text-primary" : "text-muted-foreground"
3863
+ )
3864
+ }
3865
+ ),
3866
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "min-w-0 flex-1", children: [
3867
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "flex items-center gap-1 text-sm font-medium", children: [
3868
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "min-w-0 break-words", children: o.label }),
3869
+ selected && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react12.Check, { className: "h-3.5 w-3.5 flex-shrink-0 text-primary" })
3870
+ ] }),
3871
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "block text-xs text-muted-foreground", children: o.says })
3872
+ ] })
3873
+ ]
3874
+ }
3875
+ ),
3876
+ o.hint && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3877
+ InfoHint,
3878
+ {
3879
+ text: o.hint,
3880
+ label: `About ${o.label}`,
3881
+ className: "absolute right-2 top-2.5"
3882
+ }
3883
+ )
3884
+ ] }, o.choice);
3885
+ })
3886
+ }
3887
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-sm", "data-who-can-see-text": true, children: [
3888
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "font-medium", children: [
3889
+ current.label,
3890
+ "."
3891
+ ] }),
3892
+ " ",
3893
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-xs text-muted-foreground", children: current.says }),
3894
+ current.hint && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InfoHint, { text: current.hint, label: `About ${current.label}` })
3895
+ ] }),
3896
+ said && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3897
+ "p",
3898
+ {
3899
+ className: (0, import_cn5.cn)(
3900
+ "text-xs",
3901
+ said.ok ? "text-green-700 dark:text-green-300" : "text-destructive"
3902
+ ),
3903
+ "data-lane-said": true,
3904
+ children: said.text
3905
+ }
3906
+ )
3907
+ ]
3908
+ }
3909
+ );
3910
+ }
3911
+
3912
+ // src/react/sheet/SecureLinkPanel.tsx
3913
+ var import_react16 = require("react");
3914
+ var import_lucide_react13 = require("lucide-react");
3915
+ var import_design_system29 = require("@ai-matrx/design-system");
3916
+ var import_design_system30 = require("@ai-matrx/design-system");
3917
+ var import_controls10 = require("@ai-matrx/design-system/controls");
3918
+ var import_design_system31 = require("@ai-matrx/design-system");
3919
+ var import_design_system32 = require("@ai-matrx/design-system");
3920
+ var import_vault2 = require("@ai-matrx/vault");
3921
+ var import_core33 = require("@ai-matrx/sharing/core");
3922
+ var import_format2 = require("@ai-matrx/kit/format");
3923
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3924
+ var STATUS_WORDS = {
3925
+ pending: "Waiting to be opened",
3926
+ viewed: "Opened",
3927
+ revoked: "Turned off",
3928
+ expired: "Expired unopened",
3929
+ undeliverable: "Not delivered",
3930
+ locked: "Closed after wrong codes"
3931
+ };
3932
+ function when(iso) {
3933
+ if (!iso) return "";
3934
+ const d = new Date(iso);
3935
+ return Number.isNaN(d.getTime()) ? "" : d.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
3936
+ }
3937
+ function SecureLinkPanel(props) {
3938
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_vault2.IdentityGate, { identity: vaultIdentity(), children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SecureLinkForm, { ...props }) });
3939
+ }
3940
+ function SecureLinkForm({ resourceType, resourceId, resourceName }) {
3941
+ const dispatch = useAppDispatch();
3942
+ const confirmed = (0, import_vault2.useConfirmed)();
3943
+ const [options, setOptions] = (0, import_react16.useState)(null);
3944
+ const [optionsError, setOptionsError] = (0, import_react16.useState)(null);
3945
+ const [email, setEmail] = (0, import_react16.useState)("");
3946
+ const [phone, setPhone] = (0, import_react16.useState)("");
3947
+ const [linkChannel, setLinkChannel] = (0, import_react16.useState)("email");
3948
+ const [fields, setFields] = (0, import_react16.useState)([]);
3949
+ const [note, setNote] = (0, import_react16.useState)("");
3950
+ const [sending, setSending] = (0, import_react16.useState)(false);
3951
+ const [result, setResult] = (0, import_react16.useState)(null);
3952
+ const [sent, setSent] = (0, import_react16.useState)(null);
3953
+ const refreshSent = (0, import_react16.useCallback)(() => {
3954
+ (0, import_core33.listSentSecureDeliveries)(resourceType, resourceId).then(setSent).catch(() => setSent([]));
3955
+ }, [resourceType, resourceId]);
3956
+ (0, import_react16.useEffect)(() => {
3957
+ let live = true;
3958
+ (0, import_core33.fetchSecureDeliveryOptions)(dispatch, resourceType, resourceId).then((o) => live && setOptions(o)).catch(
3959
+ (err) => live && setOptionsError(
3960
+ err instanceof import_core33.SecureDeliveryRefusal ? err.message : "Could not load what can be sent."
3961
+ )
3962
+ );
3963
+ refreshSent();
3964
+ return () => {
3965
+ live = false;
3966
+ };
3967
+ }, [dispatch, resourceType, resourceId, refreshSent]);
3968
+ const [phoneTyped, setPhoneTyped] = (0, import_react16.useState)(false);
3969
+ const phonePrefilled = (0, import_react16.useRef)(false);
3970
+ (0, import_react16.useEffect)(() => {
3971
+ if (phoneTyped) return;
3972
+ const address = email.trim();
3973
+ const clearPrefill = () => {
3974
+ if (phonePrefilled.current) {
3975
+ setPhone("");
3976
+ phonePrefilled.current = false;
3977
+ }
3978
+ };
3979
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(address)) {
3980
+ clearPrefill();
3981
+ return;
3982
+ }
3983
+ let live = true;
3984
+ const timer = setTimeout(() => {
3985
+ (0, import_core33.fetchRecipientPhoneOnFile)(address).then((onFile) => {
3986
+ if (!live) return;
3987
+ if (onFile) {
3988
+ setPhone(onFile);
3989
+ phonePrefilled.current = true;
3990
+ } else {
3991
+ clearPrefill();
3992
+ }
3993
+ }).catch(() => {
3994
+ if (live) clearPrefill();
3995
+ });
3996
+ }, 400);
3997
+ return () => {
3998
+ live = false;
3999
+ clearTimeout(timer);
4000
+ };
4001
+ }, [email, phoneTyped]);
4002
+ const hasBoth = email.trim() !== "" && phone.trim() !== "";
4003
+ const canSend = email.trim() !== "" && !sending;
4004
+ async function send() {
4005
+ setSending(true);
4006
+ setResult(null);
4007
+ try {
4008
+ const receipt = await confirmed(() => (0, import_core33.sendSecureDelivery)(dispatch, {
4009
+ resource_type: resourceType,
4010
+ resource_id: resourceId,
4011
+ recipient_email: email.trim() || null,
4012
+ recipient_phone: phone.trim() || null,
4013
+ field_keys: fields,
4014
+ link_channel: hasBoth ? linkChannel : null,
4015
+ note: note.trim() || null
4016
+ }));
4017
+ setResult({ tone: receipt.same_channel ? "warn" : "ok", text: receipt.sentence });
4018
+ setEmail("");
4019
+ setPhone("");
4020
+ setPhoneTyped(false);
4021
+ phonePrefilled.current = false;
4022
+ setNote("");
4023
+ setFields([]);
4024
+ refreshSent();
4025
+ } catch (err) {
4026
+ if ((0, import_vault2.wasCancelled)(err)) {
4027
+ setResult({ tone: "warn", text: "Not sent: it was not confirmed." });
4028
+ } else if (err instanceof import_core33.SecureDeliveryRefusal) {
4029
+ setResult({ tone: "error", text: err.message, remedy: err.remedy });
4030
+ } else {
4031
+ setResult({ tone: "error", text: "The secure link could not be sent. Try again." });
4032
+ }
4033
+ } finally {
4034
+ setSending(false);
4035
+ }
4036
+ }
4037
+ async function turnOff(row) {
4038
+ const ok = await confirm({
4039
+ title: "Turn off this secure link?",
4040
+ description: `The link to ${row.recipient_email ?? row.recipient_phone ?? "this person"} stops working at once and what it carries is deleted. They will have to ask you for a new one.`,
4041
+ confirmLabel: "Turn it off"
4042
+ });
4043
+ if (!ok) return;
4044
+ try {
4045
+ await (0, import_core33.revokeSecureDelivery)(dispatch, row.id);
4046
+ } finally {
4047
+ refreshSent();
4048
+ }
4049
+ }
4050
+ const protectedFields = options?.protected_fields ?? [];
4051
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-4", children: [
4052
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-start gap-2 rounded-lg border border-border bg-muted/30 p-3", children: [
4053
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react13.ShieldCheck, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-primary" }),
4054
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
4055
+ "They get a link that opens once, and a code on a separate channel. No account needed.",
4056
+ options ? ` The link works for ${(0, import_format2.formatDurationMinutes)(options.link_ttl_minutes, { style: "long" })}.` : ""
4057
+ ] })
4058
+ ] }),
4059
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid gap-3 sm:grid-cols-2", children: [
4060
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-1", children: [
4061
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_design_system31.Label, { htmlFor: "secure-email", className: "text-xs", children: "Their email" }),
4062
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4063
+ import_controls10.Input,
4064
+ {
4065
+ id: "secure-email",
4066
+ type: "email",
4067
+ autoComplete: "off",
4068
+ value: email,
4069
+ onChange: (e) => setEmail(e.target.value),
4070
+ placeholder: "name@example.com"
4071
+ }
4072
+ )
4073
+ ] }),
4074
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-1", children: [
4075
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_design_system31.Label, { htmlFor: "secure-phone", className: "text-xs", children: "Their mobile, for the code" }),
4076
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4077
+ import_controls10.Input,
4078
+ {
4079
+ id: "secure-phone",
4080
+ type: "tel",
4081
+ autoComplete: "off",
4082
+ value: phone,
4083
+ onChange: (e) => {
4084
+ setPhone(e.target.value);
4085
+ setPhoneTyped(true);
4086
+ phonePrefilled.current = false;
4087
+ },
4088
+ placeholder: "+1 310 555 0123"
4089
+ }
4090
+ )
4091
+ ] })
4092
+ ] }),
4093
+ hasBoth ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
4094
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Send the link by" }),
4095
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4096
+ import_design_system22.ToggleGroup,
4097
+ {
4098
+ type: "single",
4099
+ size: "sm",
4100
+ variant: "outline",
4101
+ value: linkChannel,
4102
+ onValueChange: (v) => v && setLinkChannel(v),
4103
+ children: [
4104
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_design_system22.ToggleGroupItem, { value: "email", "aria-label": "Link by email", children: [
4105
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react13.Mail, { className: "mr-1 h-3.5 w-3.5" }),
4106
+ " Email"
4107
+ ] }),
4108
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_design_system22.ToggleGroupItem, { value: "sms", "aria-label": "Link by text", children: [
4109
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react13.MessageSquare, { className: "mr-1 h-3.5 w-3.5" }),
4110
+ " Text"
4111
+ ] })
4112
+ ]
4113
+ }
4114
+ ),
4115
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { children: [
4116
+ "and the code by ",
4117
+ linkChannel === "email" ? "text" : "email",
4118
+ "."
4119
+ ] })
4120
+ ] }) : email.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-xs text-amber-700 dark:text-amber-400", children: "Without a mobile number, the link and the code both go by email \u2014 anyone who can read that inbox could open it." }) : null,
4121
+ optionsError ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-xs text-destructive", children: [
4122
+ optionsError,
4123
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ErrorAlchemyMenu, { error: optionsError, operation: "Load the secure-link options" })
4124
+ ] }) : null,
4125
+ protectedFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("fieldset", { className: "space-y-2", children: [
4126
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("legend", { className: "text-xs font-medium", children: [
4127
+ "Include protected fields from ",
4128
+ resourceName
4129
+ ] }),
4130
+ protectedFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-start gap-2", children: [
4131
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4132
+ import_design_system30.Checkbox,
4133
+ {
4134
+ size: "sm",
4135
+ id: `secure-field-${f.key}`,
4136
+ checked: fields.includes(f.key),
4137
+ disabled: !f.available,
4138
+ onCheckedChange: (checked) => setFields(
4139
+ (cur) => checked ? [...cur, f.key] : cur.filter((k) => k !== f.key)
4140
+ )
4141
+ }
4142
+ ),
4143
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
4144
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_design_system31.Label, { htmlFor: `secure-field-${f.key}`, className: "text-sm", children: f.label }),
4145
+ !f.available && f.reason ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-xs text-muted-foreground", children: f.reason }) : null
4146
+ ] })
4147
+ ] }, f.key))
4148
+ ] }) : null,
4149
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-1", children: [
4150
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_design_system31.Label, { htmlFor: "secure-note", className: "text-xs", children: "Message (optional, shown once with it)" }),
4151
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4152
+ import_design_system32.Textarea,
4153
+ {
4154
+ id: "secure-note",
4155
+ rows: 2,
4156
+ maxLength: 1e3,
4157
+ value: note,
4158
+ onChange: (e) => setNote(e.target.value)
4159
+ }
4160
+ )
4161
+ ] }),
4162
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_design_system29.Button, { className: "w-full", onClick: send, disabled: !canSend, children: [
4163
+ sending ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react13.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null,
4164
+ "Send secure link"
4165
+ ] }),
4166
+ result ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4167
+ "div",
4168
+ {
4169
+ className: result.tone === "error" ? "rounded-md border border-destructive/30 bg-destructive/10 p-2.5 text-sm text-destructive-ink" : result.tone === "warn" ? "rounded-md border border-amber-500/30 bg-amber-500/10 p-2.5 text-sm" : "rounded-md border border-border bg-muted/40 p-2.5 text-sm",
4170
+ children: [
4171
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: result.text }),
4172
+ result.remedy ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "mt-1 text-xs opacity-80", children: result.remedy }) : null
4173
+ ]
4174
+ }
4175
+ ) : null,
4176
+ sent && sent.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { children: [
4177
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "mb-2 text-sm font-medium", children: "Sent securely" }),
4178
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "divide-y divide-border rounded-lg border border-border", children: sent.map((row) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: "flex items-center justify-between gap-2 p-2.5 text-sm", children: [
4179
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "min-w-0", children: [
4180
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "truncate", children: row.recipient_email ?? row.recipient_phone }),
4181
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
4182
+ STATUS_WORDS[row.status] ?? row.status,
4183
+ row.status === "viewed" && row.viewed_at ? ` ${when(row.viewed_at)}` : "",
4184
+ row.status === "pending" ? ` \xB7 until ${when(row.expires_at)}` : "",
4185
+ row.field_keys.length > 0 ? ` \xB7 with ${row.field_keys.join(", ")}` : ""
4186
+ ] })
4187
+ ] }),
4188
+ row.status === "pending" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_design_system29.Button, { size: "sm", variant: "outline", onClick: () => void turnOff(row), children: "Turn off" }) : null
4189
+ ] }, row.id)) })
4190
+ ] }) : null
4191
+ ] });
4192
+ }
4193
+
4194
+ // src/react/sheet/ShareModal.tsx
4195
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4196
+ function ShareModal({
4197
+ levelWording = "roles",
4198
+ isOpen,
4199
+ onClose,
4200
+ resourceType,
4201
+ resourceId,
4202
+ resourceName,
4203
+ organizationId,
4204
+ resourceNoun,
4205
+ isOwner: isOwnerOverride,
4206
+ outsideShare
4207
+ }) {
4208
+ const [activeTab, setActiveTab] = (0, import_react17.useState)("users");
4209
+ const [outsidePending, setOutsidePending] = (0, import_react17.useState)(0);
4210
+ const [emailingLink, setEmailingLink] = (0, import_react17.useState)(false);
4211
+ const [emailSent, setEmailSent] = (0, import_react17.useState)(false);
4212
+ const { toast } = useToast();
4213
+ const getShareUrl = () => {
4214
+ const path = (0, import_core35.getResourceSharePath)(resourceType, resourceId);
4215
+ if (!path) return null;
4216
+ const baseUrl = (0, import_core34.appUrl)();
4217
+ return `${baseUrl}${path}`;
4218
+ };
4219
+ const handleEmailLink = async () => {
4220
+ const shareUrl = getShareUrl();
4221
+ if (!shareUrl) {
4222
+ toast({
4223
+ title: "No shareable link for this item yet",
4224
+ description: `"${(0, import_core35.getResourceTypeLabel)(resourceType)}" has no page to open. Sharing access still works; only the emailed link is unavailable.`,
4225
+ variant: "destructive"
4226
+ });
4227
+ return;
4228
+ }
4229
+ setEmailingLink(true);
4230
+ try {
4231
+ const data = await (0, import_core34.sendShareNotice)("/sharing/email-link", {
4232
+ resourceType: (0, import_core35.getResourceTypeLabel)(resourceType),
4233
+ resourceName,
4234
+ shareUrl,
4235
+ // object-org-exempt: the saved-link notice is filed with the shared object, else where the person works
4236
+ organizationId: organizationId ?? (0, import_core34.getActiveOrgId)()
4237
+ // org-filter: write-target for the person's own saved-link notice
4238
+ });
4239
+ if (data.success) {
4240
+ setEmailSent(true);
4241
+ toast({
4242
+ title: "Email sent",
4243
+ description: "Link has been emailed to you"
4244
+ });
4245
+ setTimeout(() => setEmailSent(false), 3e3);
4246
+ } else {
4247
+ toast({
4248
+ title: "Failed to send email",
4249
+ description: data.msg || "Please try again",
4250
+ variant: "destructive"
4251
+ });
4252
+ }
4253
+ } catch (error2) {
4254
+ toast({
4255
+ title: "Error",
4256
+ description: "Failed to send email",
4257
+ variant: "destructive"
4258
+ });
4259
+ } finally {
4260
+ setEmailingLink(false);
4261
+ }
4262
+ };
4263
+ const {
4264
+ isOwner: resolvedIsOwner,
4265
+ loading: ownerLoading,
4266
+ error: ownerError
4267
+ } = useIsOwner(resourceType, resourceId);
4268
+ const hasOverride = typeof isOwnerOverride === "boolean";
4269
+ const isOwner = hasOverride ? isOwnerOverride : resolvedIsOwner;
4270
+ const resolvingOwner = !hasOverride && ownerLoading;
4271
+ usePageCapture(
4272
+ () => dialogCapture({
4273
+ title: `Share ${resourceName ?? resourceType}`,
4274
+ route: typeof window !== "undefined" ? window.location.pathname : "",
4275
+ dialog: "Share",
4276
+ subject: { id: resourceId, name: resourceName ?? null },
4277
+ selection: {
4278
+ "Resource type": resourceType,
4279
+ Organization: { id: organizationId ?? null, name: null },
4280
+ Tab: activeTab,
4281
+ "Outside sharing offered": outsideShare ? "yes" : "no"
4282
+ },
4283
+ errors: [ownerError ? `Ownership could not be read: ${String(ownerError)}` : null],
4284
+ sections: [
4285
+ {
4286
+ id: "share-state",
4287
+ title: "Share dialog state",
4288
+ role: "data",
4289
+ value: {
4290
+ is_owner: isOwner,
4291
+ resolving_owner: resolvingOwner,
4292
+ outside_pending_invitations: outsidePending,
4293
+ share_link: (0, import_core35.getResourceSharePath)(resourceType, resourceId),
4294
+ outside_share: outsideShare ?? null
4295
+ }
4296
+ }
4297
+ ]
4298
+ }),
4299
+ { enabled: isOpen }
4300
+ );
4301
+ const grantable = useGrantableLevels(resourceType, resourceId, isOpen);
4302
+ const canShare = isOwner || grantable.maySharePeople;
4303
+ const ownerUnknown = !hasOverride && !ownerLoading && ownerError !== null;
4304
+ const registryEntry = resourceType ? (0, import_core35.getShareableResource)(resourceType) : void 0;
4305
+ const configError = !resourceId ? "No resource id was supplied to the share dialog." : !registryEntry ? `"${resourceType}" is not a registered shareable resource. Add it to shareable_resource_registry.` : null;
4306
+ const {
4307
+ permissions,
4308
+ isPublic: resourceIsPublic,
4309
+ shownTo,
4310
+ childRecord,
4311
+ setShownTo,
4312
+ organizationDefault,
4313
+ whoCanSee,
4314
+ setWhoCanSee,
4315
+ loading,
4316
+ error,
4317
+ shareWithUser: shareWithUser2,
4318
+ makePublic: makePublic2,
4319
+ revokeAccess: revokeAccess2,
4320
+ updateLevel,
4321
+ refresh,
4322
+ homeOrganizationId
4323
+ } = useSharing(
4324
+ resourceType,
4325
+ resourceId,
4326
+ isOpen && !configError,
4327
+ resourceName,
4328
+ // The object's organization, as the page that opened this resolved it — the share's
4329
+ // notification is filed there and never asks which workspace this is for.
4330
+ organizationId ?? null
4331
+ );
4332
+ const contactsOrganizationId = organizationId ?? homeOrganizationId ?? void 0;
4333
+ const userPermissions = permissions.filter((p) => p.grantedToUserId);
4334
+ const publicPermission = permissions.find((p) => p.isPublic);
4335
+ const resourceLabel = resourceNoun ?? (0, import_core35.getResourceTypeLabel)(resourceType);
4336
+ const manageBlockedNotice = ownerUnknown ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "p-3 rounded-lg border border-destructive/20 bg-destructive/10 flex items-start gap-2", children: [
4337
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react15.AlertTriangle, { className: "h-4 w-4 text-destructive flex-shrink-0 mt-0.5" }),
4338
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
4339
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "text-sm font-medium text-destructive", children: [
4340
+ "Couldn't confirm whether you may change sharing on this",
4341
+ " ",
4342
+ resourceLabel.toLowerCase()
4343
+ ] }),
4344
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "text-xs text-destructive/80 mt-0.5", children: [
4345
+ ownerError,
4346
+ " ",
4347
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ErrorAlchemyMenu, { error: ownerError })
4348
+ ] })
4349
+ ] })
4350
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "p-3 rounded-lg border bg-muted/30 flex items-start gap-2", children: [
4351
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react15.Lock, { className: "h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5" }),
4352
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
4353
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "text-sm font-medium", children: [
4354
+ "You need Full access on this ",
4355
+ resourceLabel.toLowerCase(),
4356
+ " to change who can see it"
4357
+ ] }),
4358
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Deciding who else may is what Full access means (View < Comment < Edit < Full access). Ask whoever has it, or an owner of the organization. The Access tab still shows you everyone who can reach this and why." })
4359
+ ] })
4360
+ ] });
4361
+ const shareBlockedNotice = grantable.loading ? null : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "p-3 rounded-lg border bg-muted/30 flex items-start gap-2", children: [
4362
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react15.Lock, { className: "h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5" }),
4363
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm", children: grantable.error ?? grantable.levels.viewer ?? "You need Edit or Full access on this to share it." })
4364
+ ] });
4365
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system2.Dialog, { open: isOpen, onOpenChange: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_design_system2.DialogContent, { className: "max-w-2xl max-h-[90dvh] flex flex-col", children: [
4366
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system2.DialogHeader, { className: "flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-start justify-between gap-2 pr-10", children: [
4367
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 min-w-0", children: [
4368
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_design_system2.DialogTitle, { className: "flex items-center gap-1.5", children: [
4369
+ "Share ",
4370
+ resourceLabel,
4371
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PageCaptureButton, { size: "xs" })
4372
+ ] }),
4373
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system2.DialogDescription, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-sm text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4374
+ EntityRef,
4375
+ {
4376
+ token: resourceType,
4377
+ id: resourceId,
4378
+ name: resourceName,
4379
+ href: (0, import_core35.getResourceSharePath)(resourceType, resourceId) ?? void 0,
4380
+ showIcon: false
4381
+ }
4382
+ ) }) })
4383
+ ] }),
4384
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
4385
+ import_design_system34.Button,
4386
+ {
4387
+ variant: "outline",
4388
+ size: "sm",
4389
+ onClick: handleEmailLink,
4390
+ disabled: emailingLink,
4391
+ className: "flex-shrink-0",
4392
+ children: [
4393
+ emailingLink ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.Loader2, { className: "h-4 w-4 animate-spin" }) : emailSent ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.CheckCircle, { className: "h-4 w-4 text-green-500" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.Mail, { className: "h-4 w-4" }),
4394
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ml-1.5 hidden sm:inline", children: emailSent ? "Sent!" : "Email link" })
4395
+ ]
4396
+ }
4397
+ )
4398
+ ] }) }),
4399
+ configError && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 py-10 px-6 text-center", children: [
4400
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react15.AlertTriangle, { className: "h-8 w-8 text-destructive" }),
4401
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm font-medium", children: "Sharing is unavailable" }),
4402
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
4403
+ configError,
4404
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ErrorAlchemyMenu, { error: configError })
4405
+ ] })
4406
+ ] }),
4407
+ !configError && resolvingOwner && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 space-y-3 py-4", "aria-busy": "true", children: [
4408
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system35.Skeleton, { className: "h-9 w-full" }),
4409
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system35.Skeleton, { className: "h-24 w-full" }),
4410
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system35.Skeleton, { className: "h-32 w-full" })
4411
+ ] }),
4412
+ !configError && !resolvingOwner && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
4413
+ import_design_system33.Tabs,
4414
+ {
4415
+ value: activeTab,
4416
+ onValueChange: (value) => setActiveTab(value),
4417
+ className: "flex-1 flex flex-col min-h-0",
4418
+ children: [
4419
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_controls11.TabsList, { fill: true, className: "flex-shrink-0", children: [
4420
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(ShareTabsTrigger, { value: "users", children: [
4421
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.Users, { className: "w-4 h-4" }),
4422
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hidden sm:inline", children: "Users" }),
4423
+ userPermissions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ml-1 px-1.5 py-0.5 text-xs bg-primary/10 rounded-full", children: userPermissions.length })
4424
+ ] }),
4425
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(ShareTabsTrigger, { value: "public", children: [
4426
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.Globe, { className: "w-4 h-4" }),
4427
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hidden sm:inline", children: "Anyone link" }),
4428
+ publicPermission && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ml-1 px-1.5 py-0.5 text-xs bg-green-500/10 rounded-full", children: "\u2022" })
4429
+ ] }),
4430
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(ShareTabsTrigger, { value: "secure", children: [
4431
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.ShieldCheck, { className: "w-4 h-4" }),
4432
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hidden sm:inline", children: "Secure link" })
4433
+ ] }),
4434
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(ShareTabsTrigger, { value: "access", children: [
4435
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react14.KeyRound, { className: "w-4 h-4" }),
4436
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "hidden sm:inline", children: "Access" })
4437
+ ] })
4438
+ ] }),
4439
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dialog-scroll-clear-floating flex-1 mt-3 min-h-0 overflow-y-auto", children: [
4440
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_design_system33.TabsContent, { value: "users", className: "mt-0 space-y-3", children: [
4441
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4442
+ WhoCanSeeThis,
4443
+ {
4444
+ whoCanSee,
4445
+ canChange: canShare,
4446
+ onChoose: setWhoCanSee,
4447
+ row: {
4448
+ resourceType,
4449
+ resourceId,
4450
+ shownTo,
4451
+ isPublic: resourceIsPublic,
4452
+ childRecord,
4453
+ // A whole Table is a `record` too and is never published to the web (no page
4454
+ // of its own; only its rows have one): no switch is drawn for it.
4455
+ ...resourceType === "record" && resourceNoun === "Table" ? { publishable: false } : {},
4456
+ onSetShownTo: setShownTo,
4457
+ onPublish: () => makePublic2(),
4458
+ onStopPublishing: () => revokeAccess2({ isPublic: true })
4459
+ }
4460
+ }
4461
+ ),
4462
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
4463
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "text-sm font-medium mb-2", children: "Current Access" }),
4464
+ !isOwner && !loading && userPermissions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "rounded-lg border bg-muted/30 p-3 text-xs text-muted-foreground", children: "The list of people with access is visible to the owner and Admins." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4465
+ PermissionsList,
4466
+ {
4467
+ permissions: userPermissions,
4468
+ isOwner,
4469
+ onUpdateLevel: updateLevel,
4470
+ onRevoke: revokeAccess2,
4471
+ loading,
4472
+ alsoPending: {
4473
+ count: outsidePending,
4474
+ one: "person outside this organization",
4475
+ many: "people outside this organization"
4476
+ }
4477
+ }
4478
+ ),
4479
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4480
+ OrgAvailabilityNote,
4481
+ {
4482
+ permissions: permissions.filter(
4483
+ (p) => p.grantedToOrganizationId
4484
+ ),
4485
+ organizationDefault
4486
+ }
4487
+ )
4488
+ ] }),
4489
+ canShare ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4490
+ ShareWithUserTab,
4491
+ {
4492
+ levelWording,
4493
+ grantable,
4494
+ onShare: shareWithUser2,
4495
+ onSuccess: refresh,
4496
+ resourceType,
4497
+ resourceId,
4498
+ ...contactsOrganizationId ? { organizationId: contactsOrganizationId } : {},
4499
+ alreadySharedUserIds: userPermissions.map((p) => p.grantedToUserId).filter((id) => !!id),
4500
+ ...outsideShare ? {
4501
+ grantEveryonePerson: async (person, level) => {
4502
+ try {
4503
+ const answer = await (0, import_core36.shareWithOutsidePerson)(
4504
+ outsideShare.organizationId,
4505
+ outsideShare.tableId,
4506
+ person.email,
4507
+ level
4508
+ );
4509
+ return answer.granted ? { success: true, message: answer.say } : {
4510
+ success: false,
4511
+ error: answer.say || "Invited, not yet given access"
4512
+ };
4513
+ } catch (err) {
4514
+ return {
4515
+ success: false,
4516
+ error: err instanceof Error ? err.message : "Not shared"
4517
+ };
4518
+ }
4519
+ }
4520
+ } : {}
4521
+ }
4522
+ ) : shareBlockedNotice,
4523
+ outsideShare ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4524
+ OutsideSharePanel,
4525
+ {
4526
+ organizationId: outsideShare.organizationId,
4527
+ tableId: outsideShare.tableId,
4528
+ tableName: resourceName,
4529
+ onPendingChange: setOutsidePending,
4530
+ onGranted: refresh
4531
+ }
4532
+ ) : null
4533
+ ] }),
4534
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_design_system33.TabsContent, { value: "access", className: "mt-0", children: [
4535
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4536
+ AccessSummaryPanel,
4537
+ {
4538
+ entityType: resourceType,
4539
+ entityId: resourceId,
4540
+ enabled: activeTab === "access",
4541
+ refreshToken: permissions.map((p) => `${p.id}:${p.permissionLevel}`).join("|")
4542
+ }
4543
+ ),
4544
+ resourceType === "record" && isOwner ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(WhoCouldSeeOn, { recordId: resourceId }) : null
4545
+ ] }),
4546
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system33.TabsContent, { value: "secure", className: "mt-0", children: isOwner ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4547
+ SecureLinkPanel,
4548
+ {
4549
+ resourceType,
4550
+ resourceId,
4551
+ resourceName
4552
+ }
4553
+ ) : manageBlockedNotice }),
4554
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_design_system33.TabsContent, { value: "public", className: "mt-0", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4555
+ PublicAccessTab,
4556
+ {
4557
+ publicPermission,
4558
+ isOwner,
4559
+ resourceType,
4560
+ resourceId,
4561
+ resourceName
4562
+ }
4563
+ ) })
4564
+ ] })
4565
+ ]
4566
+ }
4567
+ ),
4568
+ error && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "mt-3 p-2.5 bg-destructive/10 border border-destructive/20 rounded-md flex-shrink-0", children: [
4569
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm text-destructive", children: error }),
4570
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ErrorAlchemyMenu, { error })
4571
+ ] })
4572
+ ] }) });
4573
+ }
4574
+ //# sourceMappingURL=react.cjs.map