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