@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.mjs
CHANGED
|
@@ -1368,6 +1368,10 @@ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-run
|
|
|
1368
1368
|
var DEFAULT_CONNECT_ERROR = "Couldn't start the connection. Please try again.";
|
|
1369
1369
|
var DEFAULT_DISCONNECT_ERROR = "Couldn't disconnect. Please try again.";
|
|
1370
1370
|
var DEFAULT_LOAD_ERROR = "Couldn't load connections. Please try again.";
|
|
1371
|
+
var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
|
|
1372
|
+
var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
|
|
1373
|
+
var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
|
|
1374
|
+
var DEFAULT_ORG_DISABLED_REASON = "Org-wide connections aren't available to you.";
|
|
1371
1375
|
var SKELETON_COUNT = 3;
|
|
1372
1376
|
function capabilityLabel(capability) {
|
|
1373
1377
|
if (capability === "read") return "Read only";
|
|
@@ -1382,6 +1386,17 @@ function formatSince(since) {
|
|
|
1382
1386
|
function errorMessageFrom(err, fallback) {
|
|
1383
1387
|
return err instanceof Error && err.message ? err.message : fallback;
|
|
1384
1388
|
}
|
|
1389
|
+
function ownerLabel(entry) {
|
|
1390
|
+
const connection = entry.connection;
|
|
1391
|
+
if (connection.status !== "connected") return null;
|
|
1392
|
+
if (connection.kind === "org") return "Org";
|
|
1393
|
+
if (connection.owned_by_viewer) return "Yours";
|
|
1394
|
+
if (connection.shared_by) return `Shared by ${connection.shared_by.name}`;
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
function isSharedWithViewer(entry) {
|
|
1398
|
+
return entry.connection.status === "connected" && Boolean(entry.connection.shared_by) && !entry.connection.owned_by_viewer;
|
|
1399
|
+
}
|
|
1385
1400
|
function ConnectorPicker({
|
|
1386
1401
|
client,
|
|
1387
1402
|
purpose,
|
|
@@ -1393,6 +1408,7 @@ function ConnectorPicker({
|
|
|
1393
1408
|
const [entries, setEntries] = useState7(null);
|
|
1394
1409
|
const [loadError, setLoadError] = useState7(null);
|
|
1395
1410
|
const [uiState, setUiState] = useState7({});
|
|
1411
|
+
const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
|
|
1396
1412
|
const clientRef = useRef5(client);
|
|
1397
1413
|
clientRef.current = client;
|
|
1398
1414
|
const purposeRef = useRef5(purpose);
|
|
@@ -1451,10 +1467,10 @@ function ConnectorPicker({
|
|
|
1451
1467
|
};
|
|
1452
1468
|
}, [gated, refreshAfterReturn]);
|
|
1453
1469
|
const handleConnect = useCallback4(
|
|
1454
|
-
async (entry) => {
|
|
1470
|
+
async (entry, kind) => {
|
|
1455
1471
|
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1456
1472
|
try {
|
|
1457
|
-
const result = await clientRef.current.connect(entry);
|
|
1473
|
+
const result = await clientRef.current.connect(entry, { kind });
|
|
1458
1474
|
if (result?.connect_url) {
|
|
1459
1475
|
window.open(result.connect_url, "_blank", "noopener,noreferrer");
|
|
1460
1476
|
return;
|
|
@@ -1494,121 +1510,429 @@ function ConnectorPicker({
|
|
|
1494
1510
|
},
|
|
1495
1511
|
[load]
|
|
1496
1512
|
);
|
|
1513
|
+
const listGrants = useCallback4(
|
|
1514
|
+
(entry) => clientRef.current.listGrants?.(entry) ?? Promise.resolve([]),
|
|
1515
|
+
[]
|
|
1516
|
+
);
|
|
1517
|
+
const addGrant = useCallback4(
|
|
1518
|
+
(entry, input) => clientRef.current.grant?.(entry, input) ?? Promise.resolve(),
|
|
1519
|
+
[]
|
|
1520
|
+
);
|
|
1521
|
+
const revokeGrant = useCallback4(
|
|
1522
|
+
(entry, grantId) => clientRef.current.revokeGrant?.(entry, grantId) ?? Promise.resolve(),
|
|
1523
|
+
[]
|
|
1524
|
+
);
|
|
1497
1525
|
if (gated) return null;
|
|
1498
1526
|
const containerClassName = cn(
|
|
1499
1527
|
"flex flex-col",
|
|
1500
1528
|
layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
|
|
1501
1529
|
);
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
"
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
"
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
{
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1530
|
+
const ownEntries = entries?.filter((entry) => !isSharedWithViewer(entry)) ?? null;
|
|
1531
|
+
const sharedEntries = entries?.filter(isSharedWithViewer) ?? [];
|
|
1532
|
+
return /* @__PURE__ */ jsxs6("div", { "data-testid": "connector-picker", "data-layout": layout, className: "flex flex-col gap-4", children: [
|
|
1533
|
+
/* @__PURE__ */ jsxs6("div", { className: containerClassName, children: [
|
|
1534
|
+
entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ jsxs6(
|
|
1535
|
+
"div",
|
|
1536
|
+
{
|
|
1537
|
+
"data-testid": "connector-picker-skeleton-item",
|
|
1538
|
+
"aria-hidden": "true",
|
|
1539
|
+
className: "flex items-center gap-3 rounded-lg border border-border p-3",
|
|
1540
|
+
children: [
|
|
1541
|
+
/* @__PURE__ */ jsx7("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
|
|
1542
|
+
/* @__PURE__ */ jsx7("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
|
|
1543
|
+
]
|
|
1544
|
+
},
|
|
1545
|
+
i
|
|
1546
|
+
)),
|
|
1547
|
+
loadError && /* @__PURE__ */ jsxs6(
|
|
1548
|
+
"div",
|
|
1549
|
+
{
|
|
1550
|
+
role: "alert",
|
|
1551
|
+
"data-testid": "connector-picker-load-error",
|
|
1552
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
|
|
1553
|
+
children: [
|
|
1554
|
+
/* @__PURE__ */ jsx7("span", { children: loadError }),
|
|
1555
|
+
/* @__PURE__ */ jsx7("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
|
|
1556
|
+
]
|
|
1557
|
+
}
|
|
1558
|
+
),
|
|
1559
|
+
entries !== null && entries.length === 0 && !loadError && /* @__PURE__ */ jsx7("p", { "data-testid": "connector-picker-empty", className: "text-sm text-muted-foreground", children: "No connections available." }),
|
|
1560
|
+
ownEntries?.map((entry) => /* @__PURE__ */ jsx7(
|
|
1561
|
+
ConnectorTile,
|
|
1562
|
+
{
|
|
1563
|
+
entry,
|
|
1564
|
+
ui: uiState[entry.slug],
|
|
1565
|
+
layout,
|
|
1566
|
+
grantsSupported,
|
|
1567
|
+
onConnect: handleConnect,
|
|
1568
|
+
onDisconnect: handleDisconnect,
|
|
1569
|
+
listGrants,
|
|
1570
|
+
addGrant,
|
|
1571
|
+
revokeGrant
|
|
1572
|
+
},
|
|
1573
|
+
entry.slug
|
|
1574
|
+
))
|
|
1575
|
+
] }),
|
|
1576
|
+
sharedEntries.length > 0 && /* @__PURE__ */ jsxs6("section", { "data-testid": "connector-picker-shared-with-me", className: "flex flex-col gap-2", children: [
|
|
1577
|
+
/* @__PURE__ */ jsx7("h3", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Shared with me" }),
|
|
1578
|
+
/* @__PURE__ */ jsx7("div", { className: containerClassName, children: sharedEntries.map((entry) => /* @__PURE__ */ jsx7(
|
|
1579
|
+
ConnectorTile,
|
|
1580
|
+
{
|
|
1581
|
+
entry,
|
|
1582
|
+
ui: uiState[entry.slug],
|
|
1583
|
+
layout,
|
|
1584
|
+
shared: true,
|
|
1585
|
+
grantsSupported,
|
|
1586
|
+
onConnect: handleConnect,
|
|
1587
|
+
onDisconnect: handleDisconnect,
|
|
1588
|
+
listGrants,
|
|
1589
|
+
addGrant,
|
|
1590
|
+
revokeGrant
|
|
1591
|
+
},
|
|
1592
|
+
entry.slug
|
|
1593
|
+
)) })
|
|
1594
|
+
] })
|
|
1540
1595
|
] });
|
|
1541
1596
|
}
|
|
1542
1597
|
function ConnectorTile({
|
|
1543
1598
|
entry,
|
|
1544
1599
|
ui,
|
|
1545
1600
|
layout,
|
|
1601
|
+
shared = false,
|
|
1602
|
+
grantsSupported,
|
|
1546
1603
|
onConnect,
|
|
1547
|
-
onDisconnect
|
|
1604
|
+
onDisconnect,
|
|
1605
|
+
listGrants,
|
|
1606
|
+
addGrant,
|
|
1607
|
+
revokeGrant
|
|
1548
1608
|
}) {
|
|
1549
1609
|
const isConnected = entry.connection.status === "connected";
|
|
1550
1610
|
const isConnecting = ui?.kind === "connecting";
|
|
1551
1611
|
const isDisconnecting = ui?.kind === "disconnecting";
|
|
1552
1612
|
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1613
|
+
const [kind, setKind] = useState7("personal");
|
|
1614
|
+
const [sharingOpen, setSharingOpen] = useState7(false);
|
|
1615
|
+
const orgPermitted = entry.org_connect?.permitted === true;
|
|
1616
|
+
const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
|
|
1617
|
+
const attribution = ownerLabel(entry);
|
|
1618
|
+
const canDisconnect = isConnected && !shared;
|
|
1619
|
+
const canManageSharing = isConnected && entry.connection.owned_by_viewer === true && grantsSupported;
|
|
1553
1620
|
return /* @__PURE__ */ jsxs6(
|
|
1554
1621
|
"div",
|
|
1555
1622
|
{
|
|
1556
1623
|
"data-testid": `connector-tile-${entry.slug}`,
|
|
1557
1624
|
"data-connected": isConnected || void 0,
|
|
1558
1625
|
"data-deprecated": entry.deprecated || void 0,
|
|
1559
|
-
className: cn("flex
|
|
1626
|
+
className: cn("flex flex-col p-3", layout === "tiles" && "rounded-lg border border-border"),
|
|
1560
1627
|
children: [
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
|
|
1563
|
-
/* @__PURE__ */ jsxs6("div", { className: "
|
|
1564
|
-
/* @__PURE__ */
|
|
1565
|
-
|
|
1566
|
-
|
|
1628
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
|
|
1629
|
+
/* @__PURE__ */ jsx7(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
|
|
1630
|
+
/* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
|
|
1631
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
|
|
1632
|
+
/* @__PURE__ */ jsx7("p", { className: "truncate text-sm font-medium", children: entry.name }),
|
|
1633
|
+
entry.deprecated && /* @__PURE__ */ jsx7(
|
|
1634
|
+
"span",
|
|
1635
|
+
{
|
|
1636
|
+
"data-testid": `connector-deprecated-${entry.slug}`,
|
|
1637
|
+
className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
|
|
1638
|
+
children: "No longer available"
|
|
1639
|
+
}
|
|
1640
|
+
)
|
|
1641
|
+
] }),
|
|
1642
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1643
|
+
/* @__PURE__ */ jsx7("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
|
|
1644
|
+
entry.category && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1645
|
+
/* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
1646
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate", children: entry.category })
|
|
1647
|
+
] })
|
|
1648
|
+
] }),
|
|
1649
|
+
isConnected && /* @__PURE__ */ jsx7(
|
|
1650
|
+
"p",
|
|
1567
1651
|
{
|
|
1568
|
-
"data-testid": `connector-
|
|
1569
|
-
className: "
|
|
1570
|
-
children:
|
|
1652
|
+
"data-testid": `connector-connected-since-${entry.slug}`,
|
|
1653
|
+
className: "text-xs text-muted-foreground",
|
|
1654
|
+
children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
|
|
1571
1655
|
}
|
|
1572
|
-
)
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1656
|
+
),
|
|
1657
|
+
attribution && /* @__PURE__ */ jsx7(
|
|
1658
|
+
"p",
|
|
1659
|
+
{
|
|
1660
|
+
"data-testid": `connector-owner-${entry.slug}`,
|
|
1661
|
+
className: "text-xs text-muted-foreground",
|
|
1662
|
+
children: attribution
|
|
1663
|
+
}
|
|
1664
|
+
),
|
|
1665
|
+
!isConnected && /* @__PURE__ */ jsxs6(
|
|
1666
|
+
"div",
|
|
1667
|
+
{
|
|
1668
|
+
"data-testid": `connector-kind-toggle-${entry.slug}`,
|
|
1669
|
+
role: "group",
|
|
1670
|
+
"aria-label": "Connect as",
|
|
1671
|
+
className: "mt-1 flex items-center gap-1",
|
|
1672
|
+
children: [
|
|
1673
|
+
/* @__PURE__ */ jsx7(
|
|
1674
|
+
"button",
|
|
1675
|
+
{
|
|
1676
|
+
type: "button",
|
|
1677
|
+
"data-testid": `connector-kind-personal-${entry.slug}`,
|
|
1678
|
+
"aria-pressed": kind === "personal",
|
|
1679
|
+
disabled: isConnecting,
|
|
1680
|
+
onClick: () => setKind("personal"),
|
|
1681
|
+
className: cn(
|
|
1682
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1683
|
+
kind === "personal" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1684
|
+
"disabled:opacity-50"
|
|
1685
|
+
),
|
|
1686
|
+
children: "Personal"
|
|
1687
|
+
}
|
|
1688
|
+
),
|
|
1689
|
+
/* @__PURE__ */ jsxs6(
|
|
1690
|
+
"button",
|
|
1691
|
+
{
|
|
1692
|
+
type: "button",
|
|
1693
|
+
"data-testid": `connector-kind-org-${entry.slug}`,
|
|
1694
|
+
"aria-pressed": kind === "org",
|
|
1695
|
+
disabled: isConnecting || !orgPermitted,
|
|
1696
|
+
title: orgPermitted ? void 0 : orgDisabledReason,
|
|
1697
|
+
onClick: () => setKind("org"),
|
|
1698
|
+
className: cn(
|
|
1699
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1700
|
+
kind === "org" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1701
|
+
"disabled:opacity-50"
|
|
1702
|
+
),
|
|
1703
|
+
children: [
|
|
1704
|
+
"Org",
|
|
1705
|
+
!orgPermitted && /* @__PURE__ */ jsxs6("span", { className: "sr-only", children: [
|
|
1706
|
+
" (",
|
|
1707
|
+
orgDisabledReason,
|
|
1708
|
+
")"
|
|
1709
|
+
] })
|
|
1710
|
+
]
|
|
1711
|
+
}
|
|
1712
|
+
)
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
),
|
|
1716
|
+
errorMessage && /* @__PURE__ */ jsx7("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1580
1717
|
] }),
|
|
1581
|
-
isConnected
|
|
1582
|
-
|
|
1718
|
+
isConnected ? /* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
1719
|
+
canDisconnect && /* @__PURE__ */ jsx7(
|
|
1720
|
+
"button",
|
|
1721
|
+
{
|
|
1722
|
+
type: "button",
|
|
1723
|
+
"data-testid": `connector-disconnect-${entry.slug}`,
|
|
1724
|
+
onClick: () => onDisconnect(entry),
|
|
1725
|
+
disabled: isDisconnecting,
|
|
1726
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1727
|
+
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1728
|
+
}
|
|
1729
|
+
),
|
|
1730
|
+
canManageSharing && /* @__PURE__ */ jsx7(
|
|
1731
|
+
"button",
|
|
1732
|
+
{
|
|
1733
|
+
type: "button",
|
|
1734
|
+
"data-testid": `connector-manage-sharing-${entry.slug}`,
|
|
1735
|
+
"aria-expanded": sharingOpen,
|
|
1736
|
+
onClick: () => setSharingOpen((open) => !open),
|
|
1737
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1738
|
+
children: sharingOpen ? "Hide sharing" : "Manage sharing"
|
|
1739
|
+
}
|
|
1740
|
+
)
|
|
1741
|
+
] }) : /* @__PURE__ */ jsx7(
|
|
1742
|
+
"button",
|
|
1583
1743
|
{
|
|
1584
|
-
"
|
|
1585
|
-
|
|
1586
|
-
|
|
1744
|
+
type: "button",
|
|
1745
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1746
|
+
onClick: () => onConnect(entry, kind),
|
|
1747
|
+
disabled: isConnecting,
|
|
1748
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1749
|
+
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1587
1750
|
}
|
|
1588
|
-
)
|
|
1589
|
-
errorMessage && /* @__PURE__ */ jsx7("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1751
|
+
)
|
|
1590
1752
|
] }),
|
|
1591
|
-
|
|
1592
|
-
|
|
1753
|
+
canManageSharing && sharingOpen && /* @__PURE__ */ jsx7(
|
|
1754
|
+
ManageSharing,
|
|
1593
1755
|
{
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1599
|
-
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1756
|
+
entry,
|
|
1757
|
+
listGrants,
|
|
1758
|
+
addGrant,
|
|
1759
|
+
revokeGrant
|
|
1600
1760
|
}
|
|
1601
|
-
)
|
|
1602
|
-
|
|
1761
|
+
)
|
|
1762
|
+
]
|
|
1763
|
+
}
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
function ManageSharing({
|
|
1767
|
+
entry,
|
|
1768
|
+
listGrants,
|
|
1769
|
+
addGrant,
|
|
1770
|
+
revokeGrant
|
|
1771
|
+
}) {
|
|
1772
|
+
const [grants, setGrants] = useState7(null);
|
|
1773
|
+
const [error, setError] = useState7(null);
|
|
1774
|
+
const [busy, setBusy] = useState7(false);
|
|
1775
|
+
const [granteeType, setGranteeType] = useState7("user");
|
|
1776
|
+
const [granteeId, setGranteeId] = useState7("");
|
|
1777
|
+
const roleOptions = entry.org_connect?.role_options ?? [];
|
|
1778
|
+
const entryRef = useRef5(entry);
|
|
1779
|
+
entryRef.current = entry;
|
|
1780
|
+
const load = useCallback4(async () => {
|
|
1781
|
+
try {
|
|
1782
|
+
setGrants(await listGrants(entryRef.current));
|
|
1783
|
+
setError(null);
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
setError(errorMessageFrom(err, DEFAULT_GRANTS_LOAD_ERROR));
|
|
1786
|
+
}
|
|
1787
|
+
}, [listGrants]);
|
|
1788
|
+
useEffect6(() => {
|
|
1789
|
+
void load();
|
|
1790
|
+
}, [load]);
|
|
1791
|
+
const handleAdd = useCallback4(async () => {
|
|
1792
|
+
const id = granteeId.trim();
|
|
1793
|
+
if (!id) return;
|
|
1794
|
+
setBusy(true);
|
|
1795
|
+
try {
|
|
1796
|
+
await addGrant(entryRef.current, { granteeType, granteeId: id });
|
|
1797
|
+
setGranteeId("");
|
|
1798
|
+
await load();
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
setError(errorMessageFrom(err, DEFAULT_GRANT_ERROR));
|
|
1801
|
+
} finally {
|
|
1802
|
+
setBusy(false);
|
|
1803
|
+
}
|
|
1804
|
+
}, [addGrant, granteeId, granteeType, load]);
|
|
1805
|
+
const handleRevoke = useCallback4(
|
|
1806
|
+
async (grantId) => {
|
|
1807
|
+
setBusy(true);
|
|
1808
|
+
try {
|
|
1809
|
+
await revokeGrant(entryRef.current, grantId);
|
|
1810
|
+
await load();
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
setError(errorMessageFrom(err, DEFAULT_REVOKE_ERROR));
|
|
1813
|
+
} finally {
|
|
1814
|
+
setBusy(false);
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
[load, revokeGrant]
|
|
1818
|
+
);
|
|
1819
|
+
return /* @__PURE__ */ jsxs6(
|
|
1820
|
+
"div",
|
|
1821
|
+
{
|
|
1822
|
+
"data-testid": `connector-sharing-panel-${entry.slug}`,
|
|
1823
|
+
className: "mt-2 flex flex-col gap-2 rounded-md border border-border bg-foreground/5 p-2",
|
|
1824
|
+
children: [
|
|
1825
|
+
grants === null && !error && /* @__PURE__ */ jsx7(
|
|
1826
|
+
"p",
|
|
1603
1827
|
{
|
|
1604
|
-
|
|
1605
|
-
"
|
|
1606
|
-
|
|
1607
|
-
disabled: isConnecting,
|
|
1608
|
-
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1609
|
-
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1828
|
+
"data-testid": `connector-grants-loading-${entry.slug}`,
|
|
1829
|
+
className: "text-xs text-muted-foreground",
|
|
1830
|
+
children: "Loading sharing\u2026"
|
|
1610
1831
|
}
|
|
1611
|
-
)
|
|
1832
|
+
),
|
|
1833
|
+
error && /* @__PURE__ */ jsx7(
|
|
1834
|
+
"p",
|
|
1835
|
+
{
|
|
1836
|
+
role: "alert",
|
|
1837
|
+
"data-testid": `connector-sharing-error-${entry.slug}`,
|
|
1838
|
+
className: "text-xs text-destructive",
|
|
1839
|
+
children: error
|
|
1840
|
+
}
|
|
1841
|
+
),
|
|
1842
|
+
grants !== null && grants.length === 0 && /* @__PURE__ */ jsx7(
|
|
1843
|
+
"p",
|
|
1844
|
+
{
|
|
1845
|
+
"data-testid": `connector-grants-empty-${entry.slug}`,
|
|
1846
|
+
className: "text-xs text-muted-foreground",
|
|
1847
|
+
children: "Not shared with anyone yet."
|
|
1848
|
+
}
|
|
1849
|
+
),
|
|
1850
|
+
grants?.map((grant) => /* @__PURE__ */ jsxs6(
|
|
1851
|
+
"div",
|
|
1852
|
+
{
|
|
1853
|
+
"data-testid": `connector-grant-${entry.slug}-${grant.id}`,
|
|
1854
|
+
className: "flex items-center justify-between gap-2 text-xs",
|
|
1855
|
+
children: [
|
|
1856
|
+
/* @__PURE__ */ jsxs6("span", { className: "truncate", children: [
|
|
1857
|
+
grant.grantee_name || grant.grantee_id,
|
|
1858
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
|
|
1859
|
+
" ",
|
|
1860
|
+
"\xB7 ",
|
|
1861
|
+
grant.grantee_type === "role" ? "Role" : "User"
|
|
1862
|
+
] })
|
|
1863
|
+
] }),
|
|
1864
|
+
/* @__PURE__ */ jsx7(
|
|
1865
|
+
"button",
|
|
1866
|
+
{
|
|
1867
|
+
type: "button",
|
|
1868
|
+
"data-testid": `connector-grant-revoke-${entry.slug}-${grant.id}`,
|
|
1869
|
+
onClick: () => void handleRevoke(grant.id),
|
|
1870
|
+
disabled: busy,
|
|
1871
|
+
className: "shrink-0 rounded-md px-2 py-0.5 font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1872
|
+
children: "Revoke"
|
|
1873
|
+
}
|
|
1874
|
+
)
|
|
1875
|
+
]
|
|
1876
|
+
},
|
|
1877
|
+
grant.id
|
|
1878
|
+
)),
|
|
1879
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5", children: [
|
|
1880
|
+
/* @__PURE__ */ jsxs6(
|
|
1881
|
+
"select",
|
|
1882
|
+
{
|
|
1883
|
+
"data-testid": `connector-grant-type-${entry.slug}`,
|
|
1884
|
+
"aria-label": "Grantee type",
|
|
1885
|
+
value: granteeType,
|
|
1886
|
+
onChange: (event) => {
|
|
1887
|
+
setGranteeType(event.target.value);
|
|
1888
|
+
setGranteeId("");
|
|
1889
|
+
},
|
|
1890
|
+
disabled: busy,
|
|
1891
|
+
className: "rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
1892
|
+
children: [
|
|
1893
|
+
/* @__PURE__ */ jsx7("option", { value: "user", children: "User" }),
|
|
1894
|
+
/* @__PURE__ */ jsx7("option", { value: "role", children: "Role" })
|
|
1895
|
+
]
|
|
1896
|
+
}
|
|
1897
|
+
),
|
|
1898
|
+
granteeType === "user" ? /* @__PURE__ */ jsx7(
|
|
1899
|
+
"input",
|
|
1900
|
+
{
|
|
1901
|
+
"data-testid": `connector-grant-grantee-${entry.slug}`,
|
|
1902
|
+
"aria-label": "User ID",
|
|
1903
|
+
value: granteeId,
|
|
1904
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
1905
|
+
placeholder: "User ID",
|
|
1906
|
+
disabled: busy,
|
|
1907
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50"
|
|
1908
|
+
}
|
|
1909
|
+
) : /* @__PURE__ */ jsxs6(
|
|
1910
|
+
"select",
|
|
1911
|
+
{
|
|
1912
|
+
"data-testid": `connector-grant-role-${entry.slug}`,
|
|
1913
|
+
"aria-label": "Role",
|
|
1914
|
+
value: granteeId,
|
|
1915
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
1916
|
+
disabled: busy,
|
|
1917
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
1918
|
+
children: [
|
|
1919
|
+
/* @__PURE__ */ jsx7("option", { value: "", children: "Select a role\u2026" }),
|
|
1920
|
+
roleOptions.map((role) => /* @__PURE__ */ jsx7("option", { value: role.id, children: role.name }, role.id))
|
|
1921
|
+
]
|
|
1922
|
+
}
|
|
1923
|
+
),
|
|
1924
|
+
/* @__PURE__ */ jsx7(
|
|
1925
|
+
"button",
|
|
1926
|
+
{
|
|
1927
|
+
type: "button",
|
|
1928
|
+
"data-testid": `connector-grant-add-${entry.slug}`,
|
|
1929
|
+
onClick: () => void handleAdd(),
|
|
1930
|
+
disabled: busy || !granteeId.trim(),
|
|
1931
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1932
|
+
children: "Share"
|
|
1933
|
+
}
|
|
1934
|
+
)
|
|
1935
|
+
] })
|
|
1612
1936
|
]
|
|
1613
1937
|
}
|
|
1614
1938
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetreeve/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Shared brand-adaptive UI primitives for MindFortress frontends — min-size tokens, a favicon→logo→monogram BrandGlyph, a responsive header that keeps the search bar usable on mobile, and AppSidebar's viewport-driven auto-collapse ladder.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "./dist/index.js",
|