@meetreeve/ui 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +76 -3
- package/dist/index.d.ts +76 -3
- package/dist/index.js +407 -83
- package/dist/index.mjs +407 -83
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1423,6 +1423,10 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
|
1423
1423
|
var DEFAULT_CONNECT_ERROR = "Couldn't start the connection. Please try again.";
|
|
1424
1424
|
var DEFAULT_DISCONNECT_ERROR = "Couldn't disconnect. Please try again.";
|
|
1425
1425
|
var DEFAULT_LOAD_ERROR = "Couldn't load connections. Please try again.";
|
|
1426
|
+
var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
|
|
1427
|
+
var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
|
|
1428
|
+
var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
|
|
1429
|
+
var DEFAULT_ORG_DISABLED_REASON = "Org-wide connections aren't available to you.";
|
|
1426
1430
|
var SKELETON_COUNT = 3;
|
|
1427
1431
|
function capabilityLabel(capability) {
|
|
1428
1432
|
if (capability === "read") return "Read only";
|
|
@@ -1437,6 +1441,17 @@ function formatSince(since) {
|
|
|
1437
1441
|
function errorMessageFrom(err, fallback) {
|
|
1438
1442
|
return err instanceof Error && err.message ? err.message : fallback;
|
|
1439
1443
|
}
|
|
1444
|
+
function ownerLabel(entry) {
|
|
1445
|
+
const connection = entry.connection;
|
|
1446
|
+
if (connection.status !== "connected") return null;
|
|
1447
|
+
if (connection.kind === "org") return "Org";
|
|
1448
|
+
if (connection.owned_by_viewer) return "Yours";
|
|
1449
|
+
if (connection.shared_by) return `Shared by ${connection.shared_by.name}`;
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
function isSharedWithViewer(entry) {
|
|
1453
|
+
return entry.connection.status === "connected" && Boolean(entry.connection.shared_by) && !entry.connection.owned_by_viewer;
|
|
1454
|
+
}
|
|
1440
1455
|
function ConnectorPicker({
|
|
1441
1456
|
client,
|
|
1442
1457
|
purpose,
|
|
@@ -1448,6 +1463,7 @@ function ConnectorPicker({
|
|
|
1448
1463
|
const [entries, setEntries] = (0, import_react9.useState)(null);
|
|
1449
1464
|
const [loadError, setLoadError] = (0, import_react9.useState)(null);
|
|
1450
1465
|
const [uiState, setUiState] = (0, import_react9.useState)({});
|
|
1466
|
+
const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
|
|
1451
1467
|
const clientRef = (0, import_react9.useRef)(client);
|
|
1452
1468
|
clientRef.current = client;
|
|
1453
1469
|
const purposeRef = (0, import_react9.useRef)(purpose);
|
|
@@ -1506,10 +1522,10 @@ function ConnectorPicker({
|
|
|
1506
1522
|
};
|
|
1507
1523
|
}, [gated, refreshAfterReturn]);
|
|
1508
1524
|
const handleConnect = (0, import_react9.useCallback)(
|
|
1509
|
-
async (entry) => {
|
|
1525
|
+
async (entry, kind) => {
|
|
1510
1526
|
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1511
1527
|
try {
|
|
1512
|
-
const result = await clientRef.current.connect(entry);
|
|
1528
|
+
const result = await clientRef.current.connect(entry, { kind });
|
|
1513
1529
|
if (result?.connect_url) {
|
|
1514
1530
|
window.open(result.connect_url, "_blank", "noopener,noreferrer");
|
|
1515
1531
|
return;
|
|
@@ -1549,121 +1565,429 @@ function ConnectorPicker({
|
|
|
1549
1565
|
},
|
|
1550
1566
|
[load]
|
|
1551
1567
|
);
|
|
1568
|
+
const listGrants = (0, import_react9.useCallback)(
|
|
1569
|
+
(entry) => clientRef.current.listGrants?.(entry) ?? Promise.resolve([]),
|
|
1570
|
+
[]
|
|
1571
|
+
);
|
|
1572
|
+
const addGrant = (0, import_react9.useCallback)(
|
|
1573
|
+
(entry, input) => clientRef.current.grant?.(entry, input) ?? Promise.resolve(),
|
|
1574
|
+
[]
|
|
1575
|
+
);
|
|
1576
|
+
const revokeGrant = (0, import_react9.useCallback)(
|
|
1577
|
+
(entry, grantId) => clientRef.current.revokeGrant?.(entry, grantId) ?? Promise.resolve(),
|
|
1578
|
+
[]
|
|
1579
|
+
);
|
|
1552
1580
|
if (gated) return null;
|
|
1553
1581
|
const containerClassName = cn(
|
|
1554
1582
|
"flex flex-col",
|
|
1555
1583
|
layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
|
|
1556
1584
|
);
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
"
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
"
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
{
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1585
|
+
const ownEntries = entries?.filter((entry) => !isSharedWithViewer(entry)) ?? null;
|
|
1586
|
+
const sharedEntries = entries?.filter(isSharedWithViewer) ?? [];
|
|
1587
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "connector-picker", "data-layout": layout, className: "flex flex-col gap-4", children: [
|
|
1588
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: containerClassName, children: [
|
|
1589
|
+
entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1590
|
+
"div",
|
|
1591
|
+
{
|
|
1592
|
+
"data-testid": "connector-picker-skeleton-item",
|
|
1593
|
+
"aria-hidden": "true",
|
|
1594
|
+
className: "flex items-center gap-3 rounded-lg border border-border p-3",
|
|
1595
|
+
children: [
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
|
|
1597
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
|
|
1598
|
+
]
|
|
1599
|
+
},
|
|
1600
|
+
i
|
|
1601
|
+
)),
|
|
1602
|
+
loadError && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1603
|
+
"div",
|
|
1604
|
+
{
|
|
1605
|
+
role: "alert",
|
|
1606
|
+
"data-testid": "connector-picker-load-error",
|
|
1607
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
|
|
1608
|
+
children: [
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: loadError }),
|
|
1610
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
|
|
1611
|
+
]
|
|
1612
|
+
}
|
|
1613
|
+
),
|
|
1614
|
+
entries !== null && entries.length === 0 && !loadError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { "data-testid": "connector-picker-empty", className: "text-sm text-muted-foreground", children: "No connections available." }),
|
|
1615
|
+
ownEntries?.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1616
|
+
ConnectorTile,
|
|
1617
|
+
{
|
|
1618
|
+
entry,
|
|
1619
|
+
ui: uiState[entry.slug],
|
|
1620
|
+
layout,
|
|
1621
|
+
grantsSupported,
|
|
1622
|
+
onConnect: handleConnect,
|
|
1623
|
+
onDisconnect: handleDisconnect,
|
|
1624
|
+
listGrants,
|
|
1625
|
+
addGrant,
|
|
1626
|
+
revokeGrant
|
|
1627
|
+
},
|
|
1628
|
+
entry.slug
|
|
1629
|
+
))
|
|
1630
|
+
] }),
|
|
1631
|
+
sharedEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "data-testid": "connector-picker-shared-with-me", className: "flex flex-col gap-2", children: [
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Shared with me" }),
|
|
1633
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: containerClassName, children: sharedEntries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1634
|
+
ConnectorTile,
|
|
1635
|
+
{
|
|
1636
|
+
entry,
|
|
1637
|
+
ui: uiState[entry.slug],
|
|
1638
|
+
layout,
|
|
1639
|
+
shared: true,
|
|
1640
|
+
grantsSupported,
|
|
1641
|
+
onConnect: handleConnect,
|
|
1642
|
+
onDisconnect: handleDisconnect,
|
|
1643
|
+
listGrants,
|
|
1644
|
+
addGrant,
|
|
1645
|
+
revokeGrant
|
|
1646
|
+
},
|
|
1647
|
+
entry.slug
|
|
1648
|
+
)) })
|
|
1649
|
+
] })
|
|
1595
1650
|
] });
|
|
1596
1651
|
}
|
|
1597
1652
|
function ConnectorTile({
|
|
1598
1653
|
entry,
|
|
1599
1654
|
ui,
|
|
1600
1655
|
layout,
|
|
1656
|
+
shared = false,
|
|
1657
|
+
grantsSupported,
|
|
1601
1658
|
onConnect,
|
|
1602
|
-
onDisconnect
|
|
1659
|
+
onDisconnect,
|
|
1660
|
+
listGrants,
|
|
1661
|
+
addGrant,
|
|
1662
|
+
revokeGrant
|
|
1603
1663
|
}) {
|
|
1604
1664
|
const isConnected = entry.connection.status === "connected";
|
|
1605
1665
|
const isConnecting = ui?.kind === "connecting";
|
|
1606
1666
|
const isDisconnecting = ui?.kind === "disconnecting";
|
|
1607
1667
|
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1668
|
+
const [kind, setKind] = (0, import_react9.useState)("personal");
|
|
1669
|
+
const [sharingOpen, setSharingOpen] = (0, import_react9.useState)(false);
|
|
1670
|
+
const orgPermitted = entry.org_connect?.permitted === true;
|
|
1671
|
+
const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
|
|
1672
|
+
const attribution = ownerLabel(entry);
|
|
1673
|
+
const canDisconnect = isConnected && !shared;
|
|
1674
|
+
const canManageSharing = isConnected && entry.connection.owned_by_viewer === true && grantsSupported;
|
|
1608
1675
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1609
1676
|
"div",
|
|
1610
1677
|
{
|
|
1611
1678
|
"data-testid": `connector-tile-${entry.slug}`,
|
|
1612
1679
|
"data-connected": isConnected || void 0,
|
|
1613
1680
|
"data-deprecated": entry.deprecated || void 0,
|
|
1614
|
-
className: cn("flex
|
|
1681
|
+
className: cn("flex flex-col p-3", layout === "tiles" && "rounded-lg border border-border"),
|
|
1615
1682
|
children: [
|
|
1616
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.
|
|
1617
|
-
|
|
1618
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "
|
|
1619
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.
|
|
1620
|
-
|
|
1621
|
-
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
|
|
1685
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1686
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1687
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "truncate text-sm font-medium", children: entry.name }),
|
|
1688
|
+
entry.deprecated && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1689
|
+
"span",
|
|
1690
|
+
{
|
|
1691
|
+
"data-testid": `connector-deprecated-${entry.slug}`,
|
|
1692
|
+
className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
|
|
1693
|
+
children: "No longer available"
|
|
1694
|
+
}
|
|
1695
|
+
)
|
|
1696
|
+
] }),
|
|
1697
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
|
|
1699
|
+
entry.category && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: entry.category })
|
|
1702
|
+
] })
|
|
1703
|
+
] }),
|
|
1704
|
+
isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1705
|
+
"p",
|
|
1622
1706
|
{
|
|
1623
|
-
"data-testid": `connector-
|
|
1624
|
-
className: "
|
|
1625
|
-
children:
|
|
1707
|
+
"data-testid": `connector-connected-since-${entry.slug}`,
|
|
1708
|
+
className: "text-xs text-muted-foreground",
|
|
1709
|
+
children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
|
|
1626
1710
|
}
|
|
1627
|
-
)
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1711
|
+
),
|
|
1712
|
+
attribution && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1713
|
+
"p",
|
|
1714
|
+
{
|
|
1715
|
+
"data-testid": `connector-owner-${entry.slug}`,
|
|
1716
|
+
className: "text-xs text-muted-foreground",
|
|
1717
|
+
children: attribution
|
|
1718
|
+
}
|
|
1719
|
+
),
|
|
1720
|
+
!isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1721
|
+
"div",
|
|
1722
|
+
{
|
|
1723
|
+
"data-testid": `connector-kind-toggle-${entry.slug}`,
|
|
1724
|
+
role: "group",
|
|
1725
|
+
"aria-label": "Connect as",
|
|
1726
|
+
className: "mt-1 flex items-center gap-1",
|
|
1727
|
+
children: [
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1729
|
+
"button",
|
|
1730
|
+
{
|
|
1731
|
+
type: "button",
|
|
1732
|
+
"data-testid": `connector-kind-personal-${entry.slug}`,
|
|
1733
|
+
"aria-pressed": kind === "personal",
|
|
1734
|
+
disabled: isConnecting,
|
|
1735
|
+
onClick: () => setKind("personal"),
|
|
1736
|
+
className: cn(
|
|
1737
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1738
|
+
kind === "personal" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1739
|
+
"disabled:opacity-50"
|
|
1740
|
+
),
|
|
1741
|
+
children: "Personal"
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1745
|
+
"button",
|
|
1746
|
+
{
|
|
1747
|
+
type: "button",
|
|
1748
|
+
"data-testid": `connector-kind-org-${entry.slug}`,
|
|
1749
|
+
"aria-pressed": kind === "org",
|
|
1750
|
+
disabled: isConnecting || !orgPermitted,
|
|
1751
|
+
title: orgPermitted ? void 0 : orgDisabledReason,
|
|
1752
|
+
onClick: () => setKind("org"),
|
|
1753
|
+
className: cn(
|
|
1754
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1755
|
+
kind === "org" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1756
|
+
"disabled:opacity-50"
|
|
1757
|
+
),
|
|
1758
|
+
children: [
|
|
1759
|
+
"Org",
|
|
1760
|
+
!orgPermitted && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "sr-only", children: [
|
|
1761
|
+
" (",
|
|
1762
|
+
orgDisabledReason,
|
|
1763
|
+
")"
|
|
1764
|
+
] })
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
)
|
|
1768
|
+
]
|
|
1769
|
+
}
|
|
1770
|
+
),
|
|
1771
|
+
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1635
1772
|
] }),
|
|
1636
|
-
isConnected
|
|
1637
|
-
|
|
1773
|
+
isConnected ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
1774
|
+
canDisconnect && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1775
|
+
"button",
|
|
1776
|
+
{
|
|
1777
|
+
type: "button",
|
|
1778
|
+
"data-testid": `connector-disconnect-${entry.slug}`,
|
|
1779
|
+
onClick: () => onDisconnect(entry),
|
|
1780
|
+
disabled: isDisconnecting,
|
|
1781
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1782
|
+
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1783
|
+
}
|
|
1784
|
+
),
|
|
1785
|
+
canManageSharing && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1786
|
+
"button",
|
|
1787
|
+
{
|
|
1788
|
+
type: "button",
|
|
1789
|
+
"data-testid": `connector-manage-sharing-${entry.slug}`,
|
|
1790
|
+
"aria-expanded": sharingOpen,
|
|
1791
|
+
onClick: () => setSharingOpen((open) => !open),
|
|
1792
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1793
|
+
children: sharingOpen ? "Hide sharing" : "Manage sharing"
|
|
1794
|
+
}
|
|
1795
|
+
)
|
|
1796
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1797
|
+
"button",
|
|
1638
1798
|
{
|
|
1639
|
-
"
|
|
1640
|
-
|
|
1641
|
-
|
|
1799
|
+
type: "button",
|
|
1800
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1801
|
+
onClick: () => onConnect(entry, kind),
|
|
1802
|
+
disabled: isConnecting,
|
|
1803
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1804
|
+
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1642
1805
|
}
|
|
1643
|
-
)
|
|
1644
|
-
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1806
|
+
)
|
|
1645
1807
|
] }),
|
|
1646
|
-
|
|
1647
|
-
|
|
1808
|
+
canManageSharing && sharingOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1809
|
+
ManageSharing,
|
|
1648
1810
|
{
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1654
|
-
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1811
|
+
entry,
|
|
1812
|
+
listGrants,
|
|
1813
|
+
addGrant,
|
|
1814
|
+
revokeGrant
|
|
1655
1815
|
}
|
|
1656
|
-
)
|
|
1657
|
-
|
|
1816
|
+
)
|
|
1817
|
+
]
|
|
1818
|
+
}
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
function ManageSharing({
|
|
1822
|
+
entry,
|
|
1823
|
+
listGrants,
|
|
1824
|
+
addGrant,
|
|
1825
|
+
revokeGrant
|
|
1826
|
+
}) {
|
|
1827
|
+
const [grants, setGrants] = (0, import_react9.useState)(null);
|
|
1828
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1829
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
1830
|
+
const [granteeType, setGranteeType] = (0, import_react9.useState)("user");
|
|
1831
|
+
const [granteeId, setGranteeId] = (0, import_react9.useState)("");
|
|
1832
|
+
const roleOptions = entry.org_connect?.role_options ?? [];
|
|
1833
|
+
const entryRef = (0, import_react9.useRef)(entry);
|
|
1834
|
+
entryRef.current = entry;
|
|
1835
|
+
const load = (0, import_react9.useCallback)(async () => {
|
|
1836
|
+
try {
|
|
1837
|
+
setGrants(await listGrants(entryRef.current));
|
|
1838
|
+
setError(null);
|
|
1839
|
+
} catch (err) {
|
|
1840
|
+
setError(errorMessageFrom(err, DEFAULT_GRANTS_LOAD_ERROR));
|
|
1841
|
+
}
|
|
1842
|
+
}, [listGrants]);
|
|
1843
|
+
(0, import_react9.useEffect)(() => {
|
|
1844
|
+
void load();
|
|
1845
|
+
}, [load]);
|
|
1846
|
+
const handleAdd = (0, import_react9.useCallback)(async () => {
|
|
1847
|
+
const id = granteeId.trim();
|
|
1848
|
+
if (!id) return;
|
|
1849
|
+
setBusy(true);
|
|
1850
|
+
try {
|
|
1851
|
+
await addGrant(entryRef.current, { granteeType, granteeId: id });
|
|
1852
|
+
setGranteeId("");
|
|
1853
|
+
await load();
|
|
1854
|
+
} catch (err) {
|
|
1855
|
+
setError(errorMessageFrom(err, DEFAULT_GRANT_ERROR));
|
|
1856
|
+
} finally {
|
|
1857
|
+
setBusy(false);
|
|
1858
|
+
}
|
|
1859
|
+
}, [addGrant, granteeId, granteeType, load]);
|
|
1860
|
+
const handleRevoke = (0, import_react9.useCallback)(
|
|
1861
|
+
async (grantId) => {
|
|
1862
|
+
setBusy(true);
|
|
1863
|
+
try {
|
|
1864
|
+
await revokeGrant(entryRef.current, grantId);
|
|
1865
|
+
await load();
|
|
1866
|
+
} catch (err) {
|
|
1867
|
+
setError(errorMessageFrom(err, DEFAULT_REVOKE_ERROR));
|
|
1868
|
+
} finally {
|
|
1869
|
+
setBusy(false);
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
[load, revokeGrant]
|
|
1873
|
+
);
|
|
1874
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1875
|
+
"div",
|
|
1876
|
+
{
|
|
1877
|
+
"data-testid": `connector-sharing-panel-${entry.slug}`,
|
|
1878
|
+
className: "mt-2 flex flex-col gap-2 rounded-md border border-border bg-foreground/5 p-2",
|
|
1879
|
+
children: [
|
|
1880
|
+
grants === null && !error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1881
|
+
"p",
|
|
1658
1882
|
{
|
|
1659
|
-
|
|
1660
|
-
"
|
|
1661
|
-
|
|
1662
|
-
disabled: isConnecting,
|
|
1663
|
-
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1664
|
-
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1883
|
+
"data-testid": `connector-grants-loading-${entry.slug}`,
|
|
1884
|
+
className: "text-xs text-muted-foreground",
|
|
1885
|
+
children: "Loading sharing\u2026"
|
|
1665
1886
|
}
|
|
1666
|
-
)
|
|
1887
|
+
),
|
|
1888
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1889
|
+
"p",
|
|
1890
|
+
{
|
|
1891
|
+
role: "alert",
|
|
1892
|
+
"data-testid": `connector-sharing-error-${entry.slug}`,
|
|
1893
|
+
className: "text-xs text-destructive",
|
|
1894
|
+
children: error
|
|
1895
|
+
}
|
|
1896
|
+
),
|
|
1897
|
+
grants !== null && grants.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1898
|
+
"p",
|
|
1899
|
+
{
|
|
1900
|
+
"data-testid": `connector-grants-empty-${entry.slug}`,
|
|
1901
|
+
className: "text-xs text-muted-foreground",
|
|
1902
|
+
children: "Not shared with anyone yet."
|
|
1903
|
+
}
|
|
1904
|
+
),
|
|
1905
|
+
grants?.map((grant) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1906
|
+
"div",
|
|
1907
|
+
{
|
|
1908
|
+
"data-testid": `connector-grant-${entry.slug}-${grant.id}`,
|
|
1909
|
+
className: "flex items-center justify-between gap-2 text-xs",
|
|
1910
|
+
children: [
|
|
1911
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "truncate", children: [
|
|
1912
|
+
grant.grantee_name || grant.grantee_id,
|
|
1913
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-muted-foreground", children: [
|
|
1914
|
+
" ",
|
|
1915
|
+
"\xB7 ",
|
|
1916
|
+
grant.grantee_type === "role" ? "Role" : "User"
|
|
1917
|
+
] })
|
|
1918
|
+
] }),
|
|
1919
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1920
|
+
"button",
|
|
1921
|
+
{
|
|
1922
|
+
type: "button",
|
|
1923
|
+
"data-testid": `connector-grant-revoke-${entry.slug}-${grant.id}`,
|
|
1924
|
+
onClick: () => void handleRevoke(grant.id),
|
|
1925
|
+
disabled: busy,
|
|
1926
|
+
className: "shrink-0 rounded-md px-2 py-0.5 font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1927
|
+
children: "Revoke"
|
|
1928
|
+
}
|
|
1929
|
+
)
|
|
1930
|
+
]
|
|
1931
|
+
},
|
|
1932
|
+
grant.id
|
|
1933
|
+
)),
|
|
1934
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
1935
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1936
|
+
"select",
|
|
1937
|
+
{
|
|
1938
|
+
"data-testid": `connector-grant-type-${entry.slug}`,
|
|
1939
|
+
"aria-label": "Grantee type",
|
|
1940
|
+
value: granteeType,
|
|
1941
|
+
onChange: (event) => {
|
|
1942
|
+
setGranteeType(event.target.value);
|
|
1943
|
+
setGranteeId("");
|
|
1944
|
+
},
|
|
1945
|
+
disabled: busy,
|
|
1946
|
+
className: "rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
1947
|
+
children: [
|
|
1948
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "user", children: "User" }),
|
|
1949
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "role", children: "Role" })
|
|
1950
|
+
]
|
|
1951
|
+
}
|
|
1952
|
+
),
|
|
1953
|
+
granteeType === "user" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1954
|
+
"input",
|
|
1955
|
+
{
|
|
1956
|
+
"data-testid": `connector-grant-grantee-${entry.slug}`,
|
|
1957
|
+
"aria-label": "User ID",
|
|
1958
|
+
value: granteeId,
|
|
1959
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
1960
|
+
placeholder: "User ID",
|
|
1961
|
+
disabled: busy,
|
|
1962
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50"
|
|
1963
|
+
}
|
|
1964
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1965
|
+
"select",
|
|
1966
|
+
{
|
|
1967
|
+
"data-testid": `connector-grant-role-${entry.slug}`,
|
|
1968
|
+
"aria-label": "Role",
|
|
1969
|
+
value: granteeId,
|
|
1970
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
1971
|
+
disabled: busy,
|
|
1972
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
1973
|
+
children: [
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: "Select a role\u2026" }),
|
|
1975
|
+
roleOptions.map((role) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: role.id, children: role.name }, role.id))
|
|
1976
|
+
]
|
|
1977
|
+
}
|
|
1978
|
+
),
|
|
1979
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1980
|
+
"button",
|
|
1981
|
+
{
|
|
1982
|
+
type: "button",
|
|
1983
|
+
"data-testid": `connector-grant-add-${entry.slug}`,
|
|
1984
|
+
onClick: () => void handleAdd(),
|
|
1985
|
+
disabled: busy || !granteeId.trim(),
|
|
1986
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1987
|
+
children: "Share"
|
|
1988
|
+
}
|
|
1989
|
+
)
|
|
1990
|
+
] })
|
|
1667
1991
|
]
|
|
1668
1992
|
}
|
|
1669
1993
|
);
|