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