@meetreeve/ui 0.8.1 → 0.10.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/console/index.d.mts +65 -0
- package/dist/console/index.d.ts +65 -0
- package/dist/console/index.js +3528 -0
- package/dist/console/index.mjs +3490 -0
- package/dist/index.d.mts +170 -3
- package/dist/index.d.ts +170 -3
- package/dist/index.js +832 -83
- package/dist/index.mjs +832 -83
- package/package.json +10 -1
package/dist/index.js
CHANGED
|
@@ -1423,7 +1423,21 @@ 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.";
|
|
1430
|
+
var DEFAULT_CREDENTIAL_ERROR = "Couldn't save that credential. Please try again.";
|
|
1431
|
+
var DEFAULT_CREDENTIAL_TEST_ERROR = "Couldn't test that credential. Please try again.";
|
|
1432
|
+
var UNVERIFIABLE_CREDENTIAL_NOTE = "We can't verify this automatically \u2014 saving it is safe, but nothing has checked it yet.";
|
|
1426
1433
|
var SKELETON_COUNT = 3;
|
|
1434
|
+
var UNGROUNDED_PLACEHOLDER = /\{\{[^}]*\}\}/;
|
|
1435
|
+
function hasUngroundedPlaceholder(text) {
|
|
1436
|
+
return UNGROUNDED_PLACEHOLDER.test(text);
|
|
1437
|
+
}
|
|
1438
|
+
function isDevEnvironment() {
|
|
1439
|
+
return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
1440
|
+
}
|
|
1427
1441
|
function capabilityLabel(capability) {
|
|
1428
1442
|
if (capability === "read") return "Read only";
|
|
1429
1443
|
if (capability === "write") return "Write only";
|
|
@@ -1437,6 +1451,17 @@ function formatSince(since) {
|
|
|
1437
1451
|
function errorMessageFrom(err, fallback) {
|
|
1438
1452
|
return err instanceof Error && err.message ? err.message : fallback;
|
|
1439
1453
|
}
|
|
1454
|
+
function ownerLabel(entry) {
|
|
1455
|
+
const connection = entry.connection;
|
|
1456
|
+
if (connection.status !== "connected") return null;
|
|
1457
|
+
if (connection.kind === "org") return "Org";
|
|
1458
|
+
if (connection.owned_by_viewer) return "Yours";
|
|
1459
|
+
if (connection.shared_by) return `Shared by ${connection.shared_by.name}`;
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
function isSharedWithViewer(entry) {
|
|
1463
|
+
return entry.connection.status === "connected" && Boolean(entry.connection.shared_by) && !entry.connection.owned_by_viewer;
|
|
1464
|
+
}
|
|
1440
1465
|
function ConnectorPicker({
|
|
1441
1466
|
client,
|
|
1442
1467
|
purpose,
|
|
@@ -1448,6 +1473,9 @@ function ConnectorPicker({
|
|
|
1448
1473
|
const [entries, setEntries] = (0, import_react9.useState)(null);
|
|
1449
1474
|
const [loadError, setLoadError] = (0, import_react9.useState)(null);
|
|
1450
1475
|
const [uiState, setUiState] = (0, import_react9.useState)({});
|
|
1476
|
+
const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
|
|
1477
|
+
const credentialsSupported = typeof client.submitCredentials === "function";
|
|
1478
|
+
const credentialTestSupported = typeof client.testCredentials === "function";
|
|
1451
1479
|
const clientRef = (0, import_react9.useRef)(client);
|
|
1452
1480
|
clientRef.current = client;
|
|
1453
1481
|
const purposeRef = (0, import_react9.useRef)(purpose);
|
|
@@ -1506,10 +1534,10 @@ function ConnectorPicker({
|
|
|
1506
1534
|
};
|
|
1507
1535
|
}, [gated, refreshAfterReturn]);
|
|
1508
1536
|
const handleConnect = (0, import_react9.useCallback)(
|
|
1509
|
-
async (entry) => {
|
|
1537
|
+
async (entry, kind) => {
|
|
1510
1538
|
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1511
1539
|
try {
|
|
1512
|
-
const result = await clientRef.current.connect(entry);
|
|
1540
|
+
const result = await clientRef.current.connect(entry, { kind });
|
|
1513
1541
|
if (result?.connect_url) {
|
|
1514
1542
|
window.open(result.connect_url, "_blank", "noopener,noreferrer");
|
|
1515
1543
|
return;
|
|
@@ -1531,6 +1559,38 @@ function ConnectorPicker({
|
|
|
1531
1559
|
},
|
|
1532
1560
|
[load]
|
|
1533
1561
|
);
|
|
1562
|
+
const handleSubmitCredentials = (0, import_react9.useCallback)(
|
|
1563
|
+
async (entry, values, kind) => {
|
|
1564
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1565
|
+
try {
|
|
1566
|
+
await clientRef.current.submitCredentials?.(entry, values, { kind });
|
|
1567
|
+
const list = await load();
|
|
1568
|
+
const updated = list?.find((e) => e.slug === entry.slug) ?? null;
|
|
1569
|
+
setUiState((prev) => {
|
|
1570
|
+
const next = { ...prev };
|
|
1571
|
+
delete next[entry.slug];
|
|
1572
|
+
return next;
|
|
1573
|
+
});
|
|
1574
|
+
if (updated?.connection.status === "connected") {
|
|
1575
|
+
onConnectedRef.current?.(updated);
|
|
1576
|
+
} else {
|
|
1577
|
+
throw new Error(DEFAULT_CREDENTIAL_ERROR);
|
|
1578
|
+
}
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
setUiState((prev) => {
|
|
1581
|
+
const next = { ...prev };
|
|
1582
|
+
delete next[entry.slug];
|
|
1583
|
+
return next;
|
|
1584
|
+
});
|
|
1585
|
+
throw err;
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
[load]
|
|
1589
|
+
);
|
|
1590
|
+
const testCredentials = (0, import_react9.useCallback)(
|
|
1591
|
+
(entry, values) => clientRef.current.testCredentials?.(entry, values) ?? Promise.resolve({ valid: null }),
|
|
1592
|
+
[]
|
|
1593
|
+
);
|
|
1534
1594
|
const handleDisconnect = (0, import_react9.useCallback)(
|
|
1535
1595
|
async (entry) => {
|
|
1536
1596
|
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "disconnecting" } }));
|
|
@@ -1549,121 +1609,810 @@ function ConnectorPicker({
|
|
|
1549
1609
|
},
|
|
1550
1610
|
[load]
|
|
1551
1611
|
);
|
|
1612
|
+
const listGrants = (0, import_react9.useCallback)(
|
|
1613
|
+
(entry) => clientRef.current.listGrants?.(entry) ?? Promise.resolve([]),
|
|
1614
|
+
[]
|
|
1615
|
+
);
|
|
1616
|
+
const addGrant = (0, import_react9.useCallback)(
|
|
1617
|
+
(entry, input) => clientRef.current.grant?.(entry, input) ?? Promise.resolve(),
|
|
1618
|
+
[]
|
|
1619
|
+
);
|
|
1620
|
+
const revokeGrant = (0, import_react9.useCallback)(
|
|
1621
|
+
(entry, grantId) => clientRef.current.revokeGrant?.(entry, grantId) ?? Promise.resolve(),
|
|
1622
|
+
[]
|
|
1623
|
+
);
|
|
1552
1624
|
if (gated) return null;
|
|
1553
1625
|
const containerClassName = cn(
|
|
1554
1626
|
"flex flex-col",
|
|
1555
1627
|
layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
|
|
1556
1628
|
);
|
|
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
|
-
|
|
1629
|
+
const ownEntries = entries?.filter((entry) => !isSharedWithViewer(entry)) ?? null;
|
|
1630
|
+
const sharedEntries = entries?.filter(isSharedWithViewer) ?? [];
|
|
1631
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "connector-picker", "data-layout": layout, className: "flex flex-col gap-4", children: [
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: containerClassName, children: [
|
|
1633
|
+
entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1634
|
+
"div",
|
|
1635
|
+
{
|
|
1636
|
+
"data-testid": "connector-picker-skeleton-item",
|
|
1637
|
+
"aria-hidden": "true",
|
|
1638
|
+
className: "flex items-center gap-3 rounded-lg border border-border p-3",
|
|
1639
|
+
children: [
|
|
1640
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
|
|
1641
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
|
|
1642
|
+
]
|
|
1643
|
+
},
|
|
1644
|
+
i
|
|
1645
|
+
)),
|
|
1646
|
+
loadError && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1647
|
+
"div",
|
|
1648
|
+
{
|
|
1649
|
+
role: "alert",
|
|
1650
|
+
"data-testid": "connector-picker-load-error",
|
|
1651
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
|
|
1652
|
+
children: [
|
|
1653
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: loadError }),
|
|
1654
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
|
|
1655
|
+
]
|
|
1656
|
+
}
|
|
1657
|
+
),
|
|
1658
|
+
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." }),
|
|
1659
|
+
ownEntries?.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1660
|
+
ConnectorTile,
|
|
1661
|
+
{
|
|
1662
|
+
entry,
|
|
1663
|
+
ui: uiState[entry.slug],
|
|
1664
|
+
layout,
|
|
1665
|
+
grantsSupported,
|
|
1666
|
+
credentialsSupported,
|
|
1667
|
+
credentialTestSupported,
|
|
1668
|
+
onConnect: handleConnect,
|
|
1669
|
+
onDisconnect: handleDisconnect,
|
|
1670
|
+
onSubmitCredentials: handleSubmitCredentials,
|
|
1671
|
+
onTestCredentials: testCredentials,
|
|
1672
|
+
listGrants,
|
|
1673
|
+
addGrant,
|
|
1674
|
+
revokeGrant
|
|
1675
|
+
},
|
|
1676
|
+
entry.slug
|
|
1677
|
+
))
|
|
1678
|
+
] }),
|
|
1679
|
+
sharedEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "data-testid": "connector-picker-shared-with-me", className: "flex flex-col gap-2", children: [
|
|
1680
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Shared with me" }),
|
|
1681
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: containerClassName, children: sharedEntries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1682
|
+
ConnectorTile,
|
|
1683
|
+
{
|
|
1684
|
+
entry,
|
|
1685
|
+
ui: uiState[entry.slug],
|
|
1686
|
+
layout,
|
|
1687
|
+
shared: true,
|
|
1688
|
+
grantsSupported,
|
|
1689
|
+
credentialsSupported,
|
|
1690
|
+
credentialTestSupported,
|
|
1691
|
+
onConnect: handleConnect,
|
|
1692
|
+
onDisconnect: handleDisconnect,
|
|
1693
|
+
onSubmitCredentials: handleSubmitCredentials,
|
|
1694
|
+
onTestCredentials: testCredentials,
|
|
1695
|
+
listGrants,
|
|
1696
|
+
addGrant,
|
|
1697
|
+
revokeGrant
|
|
1698
|
+
},
|
|
1699
|
+
entry.slug
|
|
1700
|
+
)) })
|
|
1701
|
+
] })
|
|
1595
1702
|
] });
|
|
1596
1703
|
}
|
|
1597
1704
|
function ConnectorTile({
|
|
1598
1705
|
entry,
|
|
1599
1706
|
ui,
|
|
1600
1707
|
layout,
|
|
1708
|
+
shared = false,
|
|
1709
|
+
grantsSupported,
|
|
1710
|
+
credentialsSupported,
|
|
1711
|
+
credentialTestSupported,
|
|
1601
1712
|
onConnect,
|
|
1602
|
-
onDisconnect
|
|
1713
|
+
onDisconnect,
|
|
1714
|
+
onSubmitCredentials,
|
|
1715
|
+
onTestCredentials,
|
|
1716
|
+
listGrants,
|
|
1717
|
+
addGrant,
|
|
1718
|
+
revokeGrant
|
|
1603
1719
|
}) {
|
|
1604
1720
|
const isConnected = entry.connection.status === "connected";
|
|
1605
1721
|
const isConnecting = ui?.kind === "connecting";
|
|
1606
1722
|
const isDisconnecting = ui?.kind === "disconnecting";
|
|
1607
1723
|
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1724
|
+
const [kind, setKind] = (0, import_react9.useState)("personal");
|
|
1725
|
+
const [sharingOpen, setSharingOpen] = (0, import_react9.useState)(false);
|
|
1726
|
+
const [setupOpen, setSetupOpen] = (0, import_react9.useState)(false);
|
|
1727
|
+
const credentialFields = entry.credential_fields ?? [];
|
|
1728
|
+
const pasteSupported = credentialsSupported && credentialFields.length > 0;
|
|
1729
|
+
const guide = entry.guide ?? null;
|
|
1730
|
+
const canSetUp = !isConnected && (pasteSupported || guide !== null);
|
|
1731
|
+
const orgPermitted = entry.org_connect?.permitted === true;
|
|
1732
|
+
const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
|
|
1733
|
+
const attribution = ownerLabel(entry);
|
|
1734
|
+
const canDisconnect = isConnected && !shared;
|
|
1735
|
+
const canManageSharing = isConnected && entry.connection.owned_by_viewer === true && grantsSupported;
|
|
1608
1736
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1609
1737
|
"div",
|
|
1610
1738
|
{
|
|
1611
1739
|
"data-testid": `connector-tile-${entry.slug}`,
|
|
1612
1740
|
"data-connected": isConnected || void 0,
|
|
1613
1741
|
"data-deprecated": entry.deprecated || void 0,
|
|
1614
|
-
className: cn("flex
|
|
1742
|
+
className: cn("flex flex-col p-3", layout === "tiles" && "rounded-lg border border-border"),
|
|
1615
1743
|
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
|
-
|
|
1744
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
1745
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
|
|
1746
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1747
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1748
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "truncate text-sm font-medium", children: entry.name }),
|
|
1749
|
+
entry.deprecated && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1750
|
+
"span",
|
|
1751
|
+
{
|
|
1752
|
+
"data-testid": `connector-deprecated-${entry.slug}`,
|
|
1753
|
+
className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
|
|
1754
|
+
children: "No longer available"
|
|
1755
|
+
}
|
|
1756
|
+
)
|
|
1757
|
+
] }),
|
|
1758
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1759
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
|
|
1760
|
+
entry.category && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1761
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
1762
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: entry.category })
|
|
1763
|
+
] })
|
|
1764
|
+
] }),
|
|
1765
|
+
isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1766
|
+
"p",
|
|
1622
1767
|
{
|
|
1623
|
-
"data-testid": `connector-
|
|
1624
|
-
className: "
|
|
1625
|
-
children:
|
|
1768
|
+
"data-testid": `connector-connected-since-${entry.slug}`,
|
|
1769
|
+
className: "text-xs text-muted-foreground",
|
|
1770
|
+
children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
|
|
1626
1771
|
}
|
|
1627
|
-
)
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1772
|
+
),
|
|
1773
|
+
attribution && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1774
|
+
"p",
|
|
1775
|
+
{
|
|
1776
|
+
"data-testid": `connector-owner-${entry.slug}`,
|
|
1777
|
+
className: "text-xs text-muted-foreground",
|
|
1778
|
+
children: attribution
|
|
1779
|
+
}
|
|
1780
|
+
),
|
|
1781
|
+
!isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1782
|
+
"div",
|
|
1783
|
+
{
|
|
1784
|
+
"data-testid": `connector-kind-toggle-${entry.slug}`,
|
|
1785
|
+
role: "group",
|
|
1786
|
+
"aria-label": "Connect as",
|
|
1787
|
+
className: "mt-1 flex items-center gap-1",
|
|
1788
|
+
children: [
|
|
1789
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1790
|
+
"button",
|
|
1791
|
+
{
|
|
1792
|
+
type: "button",
|
|
1793
|
+
"data-testid": `connector-kind-personal-${entry.slug}`,
|
|
1794
|
+
"aria-pressed": kind === "personal",
|
|
1795
|
+
disabled: isConnecting,
|
|
1796
|
+
onClick: () => setKind("personal"),
|
|
1797
|
+
className: cn(
|
|
1798
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1799
|
+
kind === "personal" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1800
|
+
"disabled:opacity-50"
|
|
1801
|
+
),
|
|
1802
|
+
children: "Personal"
|
|
1803
|
+
}
|
|
1804
|
+
),
|
|
1805
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1806
|
+
"button",
|
|
1807
|
+
{
|
|
1808
|
+
type: "button",
|
|
1809
|
+
"data-testid": `connector-kind-org-${entry.slug}`,
|
|
1810
|
+
"aria-pressed": kind === "org",
|
|
1811
|
+
disabled: isConnecting || !orgPermitted,
|
|
1812
|
+
title: orgPermitted ? void 0 : orgDisabledReason,
|
|
1813
|
+
onClick: () => setKind("org"),
|
|
1814
|
+
className: cn(
|
|
1815
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1816
|
+
kind === "org" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1817
|
+
"disabled:opacity-50"
|
|
1818
|
+
),
|
|
1819
|
+
children: [
|
|
1820
|
+
"Org",
|
|
1821
|
+
!orgPermitted && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "sr-only", children: [
|
|
1822
|
+
" (",
|
|
1823
|
+
orgDisabledReason,
|
|
1824
|
+
")"
|
|
1825
|
+
] })
|
|
1826
|
+
]
|
|
1827
|
+
}
|
|
1828
|
+
)
|
|
1829
|
+
]
|
|
1830
|
+
}
|
|
1831
|
+
),
|
|
1832
|
+
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1635
1833
|
] }),
|
|
1636
|
-
isConnected
|
|
1637
|
-
|
|
1834
|
+
isConnected ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
1835
|
+
canDisconnect && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1836
|
+
"button",
|
|
1837
|
+
{
|
|
1838
|
+
type: "button",
|
|
1839
|
+
"data-testid": `connector-disconnect-${entry.slug}`,
|
|
1840
|
+
onClick: () => onDisconnect(entry),
|
|
1841
|
+
disabled: isDisconnecting,
|
|
1842
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1843
|
+
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1844
|
+
}
|
|
1845
|
+
),
|
|
1846
|
+
canManageSharing && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1847
|
+
"button",
|
|
1848
|
+
{
|
|
1849
|
+
type: "button",
|
|
1850
|
+
"data-testid": `connector-manage-sharing-${entry.slug}`,
|
|
1851
|
+
"aria-expanded": sharingOpen,
|
|
1852
|
+
onClick: () => setSharingOpen((open) => !open),
|
|
1853
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1854
|
+
children: sharingOpen ? "Hide sharing" : "Manage sharing"
|
|
1855
|
+
}
|
|
1856
|
+
)
|
|
1857
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
1858
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1859
|
+
"button",
|
|
1860
|
+
{
|
|
1861
|
+
type: "button",
|
|
1862
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1863
|
+
onClick: () => onConnect(entry, kind),
|
|
1864
|
+
disabled: isConnecting,
|
|
1865
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1866
|
+
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1867
|
+
}
|
|
1868
|
+
),
|
|
1869
|
+
canSetUp && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1870
|
+
"button",
|
|
1871
|
+
{
|
|
1872
|
+
type: "button",
|
|
1873
|
+
"data-testid": `connector-setup-toggle-${entry.slug}`,
|
|
1874
|
+
"aria-expanded": setupOpen,
|
|
1875
|
+
"aria-controls": `connector-setup-panel-${entry.slug}`,
|
|
1876
|
+
onClick: () => setSetupOpen((open) => !open),
|
|
1877
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1878
|
+
children: setupOpen ? "Hide setup" : pasteSupported ? "Use a token instead" : "Setup guide"
|
|
1879
|
+
}
|
|
1880
|
+
)
|
|
1881
|
+
] })
|
|
1882
|
+
] }),
|
|
1883
|
+
canSetUp && setupOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1884
|
+
"div",
|
|
1885
|
+
{
|
|
1886
|
+
id: `connector-setup-panel-${entry.slug}`,
|
|
1887
|
+
"data-testid": `connector-setup-panel-${entry.slug}`,
|
|
1888
|
+
className: "mt-2 flex flex-col gap-3 rounded-md border border-border bg-foreground/5 p-2",
|
|
1889
|
+
children: [
|
|
1890
|
+
guide && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SetupGuidePanel, { entry, guide, showVerify: !pasteSupported }),
|
|
1891
|
+
pasteSupported && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1892
|
+
CredentialForm,
|
|
1893
|
+
{
|
|
1894
|
+
entry,
|
|
1895
|
+
fields: credentialFields,
|
|
1896
|
+
kind,
|
|
1897
|
+
verify: guide?.verify ?? null,
|
|
1898
|
+
testSupported: credentialTestSupported,
|
|
1899
|
+
onSubmitCredentials,
|
|
1900
|
+
onTestCredentials
|
|
1901
|
+
}
|
|
1902
|
+
)
|
|
1903
|
+
]
|
|
1904
|
+
}
|
|
1905
|
+
),
|
|
1906
|
+
canManageSharing && sharingOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1907
|
+
ManageSharing,
|
|
1908
|
+
{
|
|
1909
|
+
entry,
|
|
1910
|
+
listGrants,
|
|
1911
|
+
addGrant,
|
|
1912
|
+
revokeGrant
|
|
1913
|
+
}
|
|
1914
|
+
)
|
|
1915
|
+
]
|
|
1916
|
+
}
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
function groundGuide(guide) {
|
|
1920
|
+
const ungrounded = [];
|
|
1921
|
+
const keep = (text) => {
|
|
1922
|
+
if (!hasUngroundedPlaceholder(text)) return true;
|
|
1923
|
+
ungrounded.push(text);
|
|
1924
|
+
return false;
|
|
1925
|
+
};
|
|
1926
|
+
const prerequisites = guide.prerequisites.filter(keep);
|
|
1927
|
+
const steps = guide.steps.filter(
|
|
1928
|
+
(step) => keep(step.text) && (!step.copyable || keep(step.copyable))
|
|
1929
|
+
);
|
|
1930
|
+
const scopeNote = keep(guide.scope_note) ? guide.scope_note : null;
|
|
1931
|
+
const verify = keep(guide.verify) ? guide.verify : null;
|
|
1932
|
+
return { prerequisites, steps, scopeNote, verify, ungrounded };
|
|
1933
|
+
}
|
|
1934
|
+
function CopyableValue({ value, testId }) {
|
|
1935
|
+
const [copied, setCopied] = (0, import_react9.useState)(false);
|
|
1936
|
+
const timer = (0, import_react9.useRef)(null);
|
|
1937
|
+
(0, import_react9.useEffect)(
|
|
1938
|
+
() => () => {
|
|
1939
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1940
|
+
},
|
|
1941
|
+
[]
|
|
1942
|
+
);
|
|
1943
|
+
const copy = (0, import_react9.useCallback)(async () => {
|
|
1944
|
+
try {
|
|
1945
|
+
if (typeof navigator.clipboard?.writeText !== "function") {
|
|
1946
|
+
throw new Error("clipboard unavailable");
|
|
1947
|
+
}
|
|
1948
|
+
await navigator.clipboard.writeText(value);
|
|
1949
|
+
setCopied(true);
|
|
1950
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1951
|
+
timer.current = setTimeout(() => setCopied(false), 1500);
|
|
1952
|
+
} catch {
|
|
1953
|
+
setCopied(false);
|
|
1954
|
+
}
|
|
1955
|
+
}, [value]);
|
|
1956
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mt-1 flex items-center gap-1.5", children: [
|
|
1957
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1958
|
+
"code",
|
|
1959
|
+
{
|
|
1960
|
+
"data-testid": `${testId}-value`,
|
|
1961
|
+
className: "min-w-0 flex-1 truncate rounded bg-foreground/10 px-1.5 py-0.5 text-[11px]",
|
|
1962
|
+
children: value
|
|
1963
|
+
}
|
|
1964
|
+
),
|
|
1965
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1966
|
+
"button",
|
|
1967
|
+
{
|
|
1968
|
+
type: "button",
|
|
1969
|
+
"data-testid": testId,
|
|
1970
|
+
onClick: () => void copy(),
|
|
1971
|
+
className: "shrink-0 rounded-md px-1.5 py-0.5 text-[11px] font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1972
|
+
children: copied ? "Copied" : "Copy"
|
|
1973
|
+
}
|
|
1974
|
+
)
|
|
1975
|
+
] });
|
|
1976
|
+
}
|
|
1977
|
+
function SetupGuidePanel({
|
|
1978
|
+
entry,
|
|
1979
|
+
guide,
|
|
1980
|
+
showVerify
|
|
1981
|
+
}) {
|
|
1982
|
+
const { prerequisites, steps, scopeNote, verify, ungrounded } = groundGuide(guide);
|
|
1983
|
+
const leaked = ungrounded.length > 0;
|
|
1984
|
+
(0, import_react9.useEffect)(() => {
|
|
1985
|
+
if (!leaked) return;
|
|
1986
|
+
console.error(
|
|
1987
|
+
`[ConnectorPicker] setup guide for "${entry.slug}" arrived with ungrounded placeholders:`,
|
|
1988
|
+
ungrounded
|
|
1989
|
+
);
|
|
1990
|
+
}, [entry.slug, guide, leaked]);
|
|
1991
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": `connector-guide-${entry.slug}`, className: "flex flex-col gap-2", children: [
|
|
1992
|
+
leaked && isDevEnvironment() && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1993
|
+
"p",
|
|
1994
|
+
{
|
|
1995
|
+
role: "alert",
|
|
1996
|
+
"data-testid": `connector-guide-ungrounded-${entry.slug}`,
|
|
1997
|
+
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1 text-xs text-destructive",
|
|
1998
|
+
children: [
|
|
1999
|
+
"Setup guide arrived with unresolved placeholders and those lines were dropped: ",
|
|
2000
|
+
ungrounded.join(" \xB7 ")
|
|
2001
|
+
]
|
|
2002
|
+
}
|
|
2003
|
+
),
|
|
2004
|
+
prerequisites.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
2005
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { className: "text-xs font-medium", children: "Before you start" }),
|
|
2006
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2007
|
+
"ul",
|
|
2008
|
+
{
|
|
2009
|
+
"data-testid": `connector-guide-prerequisites-${entry.slug}`,
|
|
2010
|
+
className: "mt-1 list-disc pl-4 text-xs text-muted-foreground",
|
|
2011
|
+
children: prerequisites.map((item) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { children: item }, item))
|
|
2012
|
+
}
|
|
2013
|
+
)
|
|
2014
|
+
] }),
|
|
2015
|
+
steps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2016
|
+
"ol",
|
|
2017
|
+
{
|
|
2018
|
+
"data-testid": `connector-guide-steps-${entry.slug}`,
|
|
2019
|
+
className: "list-decimal pl-4 text-xs",
|
|
2020
|
+
children: steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { className: "mt-1 first:mt-0", children: [
|
|
2021
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: step.text }),
|
|
2022
|
+
step.copyable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2023
|
+
CopyableValue,
|
|
2024
|
+
{
|
|
2025
|
+
value: step.copyable,
|
|
2026
|
+
testId: `connector-guide-copy-${entry.slug}-${index}`
|
|
2027
|
+
}
|
|
2028
|
+
)
|
|
2029
|
+
] }, step.text))
|
|
2030
|
+
}
|
|
2031
|
+
),
|
|
2032
|
+
scopeNote && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2033
|
+
"p",
|
|
2034
|
+
{
|
|
2035
|
+
"data-testid": `connector-guide-scope-note-${entry.slug}`,
|
|
2036
|
+
className: "rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs font-medium",
|
|
2037
|
+
children: scopeNote
|
|
2038
|
+
}
|
|
2039
|
+
),
|
|
2040
|
+
showVerify && verify && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2041
|
+
"p",
|
|
2042
|
+
{
|
|
2043
|
+
"data-testid": `connector-guide-verify-${entry.slug}`,
|
|
2044
|
+
className: "text-xs text-muted-foreground",
|
|
2045
|
+
children: verify
|
|
2046
|
+
}
|
|
2047
|
+
)
|
|
2048
|
+
] });
|
|
2049
|
+
}
|
|
2050
|
+
function CredentialForm({
|
|
2051
|
+
entry,
|
|
2052
|
+
fields,
|
|
2053
|
+
kind,
|
|
2054
|
+
verify,
|
|
2055
|
+
testSupported,
|
|
2056
|
+
onSubmitCredentials,
|
|
2057
|
+
onTestCredentials
|
|
2058
|
+
}) {
|
|
2059
|
+
const [values, setValues] = (0, import_react9.useState)(() => {
|
|
2060
|
+
const initial = {};
|
|
2061
|
+
for (const field of fields) {
|
|
2062
|
+
if (field.defaultValue) initial[field.key] = field.defaultValue;
|
|
2063
|
+
}
|
|
2064
|
+
return initial;
|
|
2065
|
+
});
|
|
2066
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
2067
|
+
const [testing, setTesting] = (0, import_react9.useState)(false);
|
|
2068
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
2069
|
+
const [result, setResult] = (0, import_react9.useState)(null);
|
|
2070
|
+
const entryRef = (0, import_react9.useRef)(entry);
|
|
2071
|
+
entryRef.current = entry;
|
|
2072
|
+
const updateField = (0, import_react9.useCallback)((key, value) => {
|
|
2073
|
+
setValues((prev) => ({ ...prev, [key]: value }));
|
|
2074
|
+
setError(null);
|
|
2075
|
+
setResult(null);
|
|
2076
|
+
}, []);
|
|
2077
|
+
const missingRequired = fields.filter((field) => field.required !== false).some((field) => !values[field.key]?.trim());
|
|
2078
|
+
const failedTest = result?.valid === false;
|
|
2079
|
+
const untested = testSupported && result === null;
|
|
2080
|
+
const fieldErrors = result?.field_errors ?? null;
|
|
2081
|
+
const handleTest = (0, import_react9.useCallback)(async () => {
|
|
2082
|
+
setTesting(true);
|
|
2083
|
+
setError(null);
|
|
2084
|
+
setResult(null);
|
|
2085
|
+
try {
|
|
2086
|
+
setResult(await onTestCredentials(entryRef.current, values));
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
setError(errorMessageFrom(err, DEFAULT_CREDENTIAL_TEST_ERROR));
|
|
2089
|
+
} finally {
|
|
2090
|
+
setTesting(false);
|
|
2091
|
+
}
|
|
2092
|
+
}, [onTestCredentials, values]);
|
|
2093
|
+
const handleSubmit = (0, import_react9.useCallback)(async () => {
|
|
2094
|
+
setBusy(true);
|
|
2095
|
+
setError(null);
|
|
2096
|
+
try {
|
|
2097
|
+
await onSubmitCredentials(entryRef.current, values, kind);
|
|
2098
|
+
} catch (err) {
|
|
2099
|
+
setError(errorMessageFrom(err, DEFAULT_CREDENTIAL_ERROR));
|
|
2100
|
+
} finally {
|
|
2101
|
+
setBusy(false);
|
|
2102
|
+
}
|
|
2103
|
+
}, [kind, onSubmitCredentials, values]);
|
|
2104
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2105
|
+
"form",
|
|
2106
|
+
{
|
|
2107
|
+
"data-testid": `connector-credential-form-${entry.slug}`,
|
|
2108
|
+
onSubmit: (event) => {
|
|
2109
|
+
event.preventDefault();
|
|
2110
|
+
if (missingRequired || busy || testing || failedTest || untested) return;
|
|
2111
|
+
void handleSubmit();
|
|
2112
|
+
},
|
|
2113
|
+
className: "flex flex-col gap-2",
|
|
2114
|
+
children: [
|
|
2115
|
+
fields.map((field) => {
|
|
2116
|
+
const inputId = `connector-credential-${entry.slug}-${field.key}`;
|
|
2117
|
+
const fieldError = fieldErrors?.[field.key] ?? null;
|
|
2118
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
2119
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { htmlFor: inputId, className: "text-xs font-medium", children: [
|
|
2120
|
+
field.label,
|
|
2121
|
+
field.required !== false && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
|
|
2122
|
+
] }),
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2124
|
+
"input",
|
|
2125
|
+
{
|
|
2126
|
+
id: inputId,
|
|
2127
|
+
"data-testid": `connector-credential-input-${entry.slug}-${field.key}`,
|
|
2128
|
+
type: field.type ?? "text",
|
|
2129
|
+
value: values[field.key] ?? "",
|
|
2130
|
+
onChange: (event) => updateField(field.key, event.target.value),
|
|
2131
|
+
placeholder: field.placeholder ?? void 0,
|
|
2132
|
+
required: field.required !== false,
|
|
2133
|
+
disabled: busy || testing,
|
|
2134
|
+
autoComplete: "off",
|
|
2135
|
+
"aria-invalid": fieldError ? true : void 0,
|
|
2136
|
+
"aria-describedby": fieldError ? `${inputId}-error` : void 0,
|
|
2137
|
+
className: cn(
|
|
2138
|
+
"rounded-md border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
2139
|
+
fieldError ? "border-destructive" : "border-border"
|
|
2140
|
+
)
|
|
2141
|
+
}
|
|
2142
|
+
),
|
|
2143
|
+
fieldError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2144
|
+
"p",
|
|
2145
|
+
{
|
|
2146
|
+
id: `${inputId}-error`,
|
|
2147
|
+
"data-testid": `connector-credential-field-error-${entry.slug}-${field.key}`,
|
|
2148
|
+
className: "text-xs text-destructive",
|
|
2149
|
+
children: fieldError
|
|
2150
|
+
}
|
|
2151
|
+
)
|
|
2152
|
+
] }, field.key);
|
|
2153
|
+
}),
|
|
2154
|
+
verify && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2155
|
+
"p",
|
|
2156
|
+
{
|
|
2157
|
+
"data-testid": `connector-guide-verify-${entry.slug}`,
|
|
2158
|
+
className: "text-xs text-muted-foreground",
|
|
2159
|
+
children: verify
|
|
2160
|
+
}
|
|
2161
|
+
),
|
|
2162
|
+
result && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CredentialTestResult, { entry, result }),
|
|
2163
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2164
|
+
"p",
|
|
2165
|
+
{
|
|
2166
|
+
role: "alert",
|
|
2167
|
+
"data-testid": `connector-credential-error-${entry.slug}`,
|
|
2168
|
+
className: "text-xs text-destructive",
|
|
2169
|
+
children: error
|
|
2170
|
+
}
|
|
2171
|
+
),
|
|
2172
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-end gap-1.5", children: [
|
|
2173
|
+
testSupported && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2174
|
+
"button",
|
|
1638
2175
|
{
|
|
1639
|
-
"
|
|
1640
|
-
|
|
1641
|
-
|
|
2176
|
+
type: "button",
|
|
2177
|
+
"data-testid": `connector-credential-test-${entry.slug}`,
|
|
2178
|
+
onClick: () => void handleTest(),
|
|
2179
|
+
disabled: missingRequired || busy || testing,
|
|
2180
|
+
className: "rounded-md border border-border px-2.5 py-1 text-xs font-medium disabled:opacity-50",
|
|
2181
|
+
children: testing ? "Testing\u2026" : "Test connection"
|
|
1642
2182
|
}
|
|
1643
2183
|
),
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
2184
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2185
|
+
"button",
|
|
2186
|
+
{
|
|
2187
|
+
type: "submit",
|
|
2188
|
+
"data-testid": `connector-credential-save-${entry.slug}`,
|
|
2189
|
+
disabled: missingRequired || busy || testing || failedTest || untested,
|
|
2190
|
+
title: failedTest ? "That credential failed its own check \u2014 fix it and test again." : untested ? "Test the credential before saving it." : void 0,
|
|
2191
|
+
className: "rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
2192
|
+
children: busy ? "Saving\u2026" : "Save credential"
|
|
2193
|
+
}
|
|
2194
|
+
)
|
|
2195
|
+
] })
|
|
2196
|
+
]
|
|
2197
|
+
}
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
function CredentialTestResult({
|
|
2201
|
+
entry,
|
|
2202
|
+
result
|
|
2203
|
+
}) {
|
|
2204
|
+
if (result.valid === true) {
|
|
2205
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2206
|
+
"div",
|
|
2207
|
+
{
|
|
2208
|
+
role: "status",
|
|
2209
|
+
"data-testid": `connector-credential-test-pass-${entry.slug}`,
|
|
2210
|
+
className: "rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs",
|
|
2211
|
+
children: [
|
|
2212
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: result.account_name ? `Verified \u2014 connected as ${result.account_name}.` : "Verified \u2014 these credentials work." }),
|
|
2213
|
+
result.permissions_note && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2214
|
+
"p",
|
|
2215
|
+
{
|
|
2216
|
+
"data-testid": `connector-credential-permissions-${entry.slug}`,
|
|
2217
|
+
className: "mt-0.5 font-medium",
|
|
2218
|
+
children: result.permissions_note
|
|
2219
|
+
}
|
|
2220
|
+
)
|
|
2221
|
+
]
|
|
2222
|
+
}
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
if (result.valid === false) {
|
|
2226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2227
|
+
"p",
|
|
2228
|
+
{
|
|
2229
|
+
role: "alert",
|
|
2230
|
+
"data-testid": `connector-credential-test-fail-${entry.slug}`,
|
|
2231
|
+
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1 text-xs text-destructive",
|
|
2232
|
+
children: result.error || "That credential didn't pass its check."
|
|
2233
|
+
}
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2237
|
+
"p",
|
|
2238
|
+
{
|
|
2239
|
+
role: "status",
|
|
2240
|
+
"data-testid": `connector-credential-test-unverified-${entry.slug}`,
|
|
2241
|
+
className: "rounded-md border border-border px-2 py-1 text-xs text-muted-foreground",
|
|
2242
|
+
children: UNVERIFIABLE_CREDENTIAL_NOTE
|
|
2243
|
+
}
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2246
|
+
function ManageSharing({
|
|
2247
|
+
entry,
|
|
2248
|
+
listGrants,
|
|
2249
|
+
addGrant,
|
|
2250
|
+
revokeGrant
|
|
2251
|
+
}) {
|
|
2252
|
+
const [grants, setGrants] = (0, import_react9.useState)(null);
|
|
2253
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
2254
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
2255
|
+
const [granteeType, setGranteeType] = (0, import_react9.useState)("user");
|
|
2256
|
+
const [granteeId, setGranteeId] = (0, import_react9.useState)("");
|
|
2257
|
+
const roleOptions = entry.org_connect?.role_options ?? [];
|
|
2258
|
+
const entryRef = (0, import_react9.useRef)(entry);
|
|
2259
|
+
entryRef.current = entry;
|
|
2260
|
+
const load = (0, import_react9.useCallback)(async () => {
|
|
2261
|
+
try {
|
|
2262
|
+
setGrants(await listGrants(entryRef.current));
|
|
2263
|
+
setError(null);
|
|
2264
|
+
} catch (err) {
|
|
2265
|
+
setError(errorMessageFrom(err, DEFAULT_GRANTS_LOAD_ERROR));
|
|
2266
|
+
}
|
|
2267
|
+
}, [listGrants]);
|
|
2268
|
+
(0, import_react9.useEffect)(() => {
|
|
2269
|
+
void load();
|
|
2270
|
+
}, [load]);
|
|
2271
|
+
const handleAdd = (0, import_react9.useCallback)(async () => {
|
|
2272
|
+
const id = granteeId.trim();
|
|
2273
|
+
if (!id) return;
|
|
2274
|
+
setBusy(true);
|
|
2275
|
+
try {
|
|
2276
|
+
await addGrant(entryRef.current, { granteeType, granteeId: id });
|
|
2277
|
+
setGranteeId("");
|
|
2278
|
+
await load();
|
|
2279
|
+
} catch (err) {
|
|
2280
|
+
setError(errorMessageFrom(err, DEFAULT_GRANT_ERROR));
|
|
2281
|
+
} finally {
|
|
2282
|
+
setBusy(false);
|
|
2283
|
+
}
|
|
2284
|
+
}, [addGrant, granteeId, granteeType, load]);
|
|
2285
|
+
const handleRevoke = (0, import_react9.useCallback)(
|
|
2286
|
+
async (grantId) => {
|
|
2287
|
+
setBusy(true);
|
|
2288
|
+
try {
|
|
2289
|
+
await revokeGrant(entryRef.current, grantId);
|
|
2290
|
+
await load();
|
|
2291
|
+
} catch (err) {
|
|
2292
|
+
setError(errorMessageFrom(err, DEFAULT_REVOKE_ERROR));
|
|
2293
|
+
} finally {
|
|
2294
|
+
setBusy(false);
|
|
2295
|
+
}
|
|
2296
|
+
},
|
|
2297
|
+
[load, revokeGrant]
|
|
2298
|
+
);
|
|
2299
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2300
|
+
"div",
|
|
2301
|
+
{
|
|
2302
|
+
"data-testid": `connector-sharing-panel-${entry.slug}`,
|
|
2303
|
+
className: "mt-2 flex flex-col gap-2 rounded-md border border-border bg-foreground/5 p-2",
|
|
2304
|
+
children: [
|
|
2305
|
+
grants === null && !error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2306
|
+
"p",
|
|
1648
2307
|
{
|
|
1649
|
-
|
|
1650
|
-
"
|
|
1651
|
-
|
|
1652
|
-
disabled: isDisconnecting,
|
|
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"
|
|
2308
|
+
"data-testid": `connector-grants-loading-${entry.slug}`,
|
|
2309
|
+
className: "text-xs text-muted-foreground",
|
|
2310
|
+
children: "Loading sharing\u2026"
|
|
1655
2311
|
}
|
|
1656
|
-
)
|
|
1657
|
-
|
|
2312
|
+
),
|
|
2313
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2314
|
+
"p",
|
|
1658
2315
|
{
|
|
1659
|
-
|
|
1660
|
-
"data-testid": `connector-
|
|
1661
|
-
|
|
1662
|
-
|
|
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"
|
|
2316
|
+
role: "alert",
|
|
2317
|
+
"data-testid": `connector-sharing-error-${entry.slug}`,
|
|
2318
|
+
className: "text-xs text-destructive",
|
|
2319
|
+
children: error
|
|
1665
2320
|
}
|
|
1666
|
-
)
|
|
2321
|
+
),
|
|
2322
|
+
grants !== null && grants.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2323
|
+
"p",
|
|
2324
|
+
{
|
|
2325
|
+
"data-testid": `connector-grants-empty-${entry.slug}`,
|
|
2326
|
+
className: "text-xs text-muted-foreground",
|
|
2327
|
+
children: "Not shared with anyone yet."
|
|
2328
|
+
}
|
|
2329
|
+
),
|
|
2330
|
+
grants?.map((grant) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2331
|
+
"div",
|
|
2332
|
+
{
|
|
2333
|
+
"data-testid": `connector-grant-${entry.slug}-${grant.id}`,
|
|
2334
|
+
className: "flex items-center justify-between gap-2 text-xs",
|
|
2335
|
+
children: [
|
|
2336
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "truncate", children: [
|
|
2337
|
+
grant.grantee_name || grant.grantee_id,
|
|
2338
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-muted-foreground", children: [
|
|
2339
|
+
" ",
|
|
2340
|
+
"\xB7 ",
|
|
2341
|
+
grant.grantee_type === "role" ? "Role" : "User"
|
|
2342
|
+
] })
|
|
2343
|
+
] }),
|
|
2344
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2345
|
+
"button",
|
|
2346
|
+
{
|
|
2347
|
+
type: "button",
|
|
2348
|
+
"data-testid": `connector-grant-revoke-${entry.slug}-${grant.id}`,
|
|
2349
|
+
onClick: () => void handleRevoke(grant.id),
|
|
2350
|
+
disabled: busy,
|
|
2351
|
+
className: "shrink-0 rounded-md px-2 py-0.5 font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
2352
|
+
children: "Revoke"
|
|
2353
|
+
}
|
|
2354
|
+
)
|
|
2355
|
+
]
|
|
2356
|
+
},
|
|
2357
|
+
grant.id
|
|
2358
|
+
)),
|
|
2359
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
2360
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2361
|
+
"select",
|
|
2362
|
+
{
|
|
2363
|
+
"data-testid": `connector-grant-type-${entry.slug}`,
|
|
2364
|
+
"aria-label": "Grantee type",
|
|
2365
|
+
value: granteeType,
|
|
2366
|
+
onChange: (event) => {
|
|
2367
|
+
setGranteeType(event.target.value);
|
|
2368
|
+
setGranteeId("");
|
|
2369
|
+
},
|
|
2370
|
+
disabled: busy,
|
|
2371
|
+
className: "rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
2372
|
+
children: [
|
|
2373
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "user", children: "User" }),
|
|
2374
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "role", children: "Role" })
|
|
2375
|
+
]
|
|
2376
|
+
}
|
|
2377
|
+
),
|
|
2378
|
+
granteeType === "user" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2379
|
+
"input",
|
|
2380
|
+
{
|
|
2381
|
+
"data-testid": `connector-grant-grantee-${entry.slug}`,
|
|
2382
|
+
"aria-label": "User ID",
|
|
2383
|
+
value: granteeId,
|
|
2384
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
2385
|
+
placeholder: "User ID",
|
|
2386
|
+
disabled: busy,
|
|
2387
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50"
|
|
2388
|
+
}
|
|
2389
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2390
|
+
"select",
|
|
2391
|
+
{
|
|
2392
|
+
"data-testid": `connector-grant-role-${entry.slug}`,
|
|
2393
|
+
"aria-label": "Role",
|
|
2394
|
+
value: granteeId,
|
|
2395
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
2396
|
+
disabled: busy,
|
|
2397
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
2398
|
+
children: [
|
|
2399
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: "Select a role\u2026" }),
|
|
2400
|
+
roleOptions.map((role) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: role.id, children: role.name }, role.id))
|
|
2401
|
+
]
|
|
2402
|
+
}
|
|
2403
|
+
),
|
|
2404
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2405
|
+
"button",
|
|
2406
|
+
{
|
|
2407
|
+
type: "button",
|
|
2408
|
+
"data-testid": `connector-grant-add-${entry.slug}`,
|
|
2409
|
+
onClick: () => void handleAdd(),
|
|
2410
|
+
disabled: busy || !granteeId.trim(),
|
|
2411
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
2412
|
+
children: "Share"
|
|
2413
|
+
}
|
|
2414
|
+
)
|
|
2415
|
+
] })
|
|
1667
2416
|
]
|
|
1668
2417
|
}
|
|
1669
2418
|
);
|