@meetreeve/ui 0.9.0 → 0.11.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 +106 -1
- package/dist/index.d.ts +106 -1
- package/dist/index.js +440 -11
- package/dist/index.mjs +443 -14
- package/package.json +10 -1
package/dist/index.js
CHANGED
|
@@ -1403,6 +1403,10 @@ function ContextSwitcher({
|
|
|
1403
1403
|
children: [
|
|
1404
1404
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BrandGlyph, { brand: toBrand(item), subtle: true }),
|
|
1405
1405
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "flex-1 truncate text-left text-foreground/90", children: item.name }),
|
|
1406
|
+
item.kindLabel && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1407
|
+
" ",
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: item.kindLabel })
|
|
1409
|
+
] }),
|
|
1406
1410
|
isActive && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Check, { className: "size-3.5 text-emerald-500" })
|
|
1407
1411
|
]
|
|
1408
1412
|
},
|
|
@@ -1427,7 +1431,17 @@ var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
|
|
|
1427
1431
|
var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
|
|
1428
1432
|
var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
|
|
1429
1433
|
var DEFAULT_ORG_DISABLED_REASON = "Org-wide connections aren't available to you.";
|
|
1434
|
+
var DEFAULT_CREDENTIAL_ERROR = "Couldn't save that credential. Please try again.";
|
|
1435
|
+
var DEFAULT_CREDENTIAL_TEST_ERROR = "Couldn't test that credential. Please try again.";
|
|
1436
|
+
var UNVERIFIABLE_CREDENTIAL_NOTE = "We can't verify this automatically \u2014 saving it is safe, but nothing has checked it yet.";
|
|
1430
1437
|
var SKELETON_COUNT = 3;
|
|
1438
|
+
var UNGROUNDED_PLACEHOLDER = /\{\{[^}]*\}\}/;
|
|
1439
|
+
function hasUngroundedPlaceholder(text) {
|
|
1440
|
+
return UNGROUNDED_PLACEHOLDER.test(text);
|
|
1441
|
+
}
|
|
1442
|
+
function isDevEnvironment() {
|
|
1443
|
+
return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
1444
|
+
}
|
|
1431
1445
|
function capabilityLabel(capability) {
|
|
1432
1446
|
if (capability === "read") return "Read only";
|
|
1433
1447
|
if (capability === "write") return "Write only";
|
|
@@ -1464,6 +1478,8 @@ function ConnectorPicker({
|
|
|
1464
1478
|
const [loadError, setLoadError] = (0, import_react9.useState)(null);
|
|
1465
1479
|
const [uiState, setUiState] = (0, import_react9.useState)({});
|
|
1466
1480
|
const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
|
|
1481
|
+
const credentialsSupported = typeof client.submitCredentials === "function";
|
|
1482
|
+
const credentialTestSupported = typeof client.testCredentials === "function";
|
|
1467
1483
|
const clientRef = (0, import_react9.useRef)(client);
|
|
1468
1484
|
clientRef.current = client;
|
|
1469
1485
|
const purposeRef = (0, import_react9.useRef)(purpose);
|
|
@@ -1547,6 +1563,38 @@ function ConnectorPicker({
|
|
|
1547
1563
|
},
|
|
1548
1564
|
[load]
|
|
1549
1565
|
);
|
|
1566
|
+
const handleSubmitCredentials = (0, import_react9.useCallback)(
|
|
1567
|
+
async (entry, values, kind) => {
|
|
1568
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1569
|
+
try {
|
|
1570
|
+
await clientRef.current.submitCredentials?.(entry, values, { kind });
|
|
1571
|
+
const list = await load();
|
|
1572
|
+
const updated = list?.find((e) => e.slug === entry.slug) ?? null;
|
|
1573
|
+
setUiState((prev) => {
|
|
1574
|
+
const next = { ...prev };
|
|
1575
|
+
delete next[entry.slug];
|
|
1576
|
+
return next;
|
|
1577
|
+
});
|
|
1578
|
+
if (updated?.connection.status === "connected") {
|
|
1579
|
+
onConnectedRef.current?.(updated);
|
|
1580
|
+
} else {
|
|
1581
|
+
throw new Error(DEFAULT_CREDENTIAL_ERROR);
|
|
1582
|
+
}
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
setUiState((prev) => {
|
|
1585
|
+
const next = { ...prev };
|
|
1586
|
+
delete next[entry.slug];
|
|
1587
|
+
return next;
|
|
1588
|
+
});
|
|
1589
|
+
throw err;
|
|
1590
|
+
}
|
|
1591
|
+
},
|
|
1592
|
+
[load]
|
|
1593
|
+
);
|
|
1594
|
+
const testCredentials = (0, import_react9.useCallback)(
|
|
1595
|
+
(entry, values) => clientRef.current.testCredentials?.(entry, values) ?? Promise.resolve({ valid: null }),
|
|
1596
|
+
[]
|
|
1597
|
+
);
|
|
1550
1598
|
const handleDisconnect = (0, import_react9.useCallback)(
|
|
1551
1599
|
async (entry) => {
|
|
1552
1600
|
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "disconnecting" } }));
|
|
@@ -1619,8 +1667,12 @@ function ConnectorPicker({
|
|
|
1619
1667
|
ui: uiState[entry.slug],
|
|
1620
1668
|
layout,
|
|
1621
1669
|
grantsSupported,
|
|
1670
|
+
credentialsSupported,
|
|
1671
|
+
credentialTestSupported,
|
|
1622
1672
|
onConnect: handleConnect,
|
|
1623
1673
|
onDisconnect: handleDisconnect,
|
|
1674
|
+
onSubmitCredentials: handleSubmitCredentials,
|
|
1675
|
+
onTestCredentials: testCredentials,
|
|
1624
1676
|
listGrants,
|
|
1625
1677
|
addGrant,
|
|
1626
1678
|
revokeGrant
|
|
@@ -1638,8 +1690,12 @@ function ConnectorPicker({
|
|
|
1638
1690
|
layout,
|
|
1639
1691
|
shared: true,
|
|
1640
1692
|
grantsSupported,
|
|
1693
|
+
credentialsSupported,
|
|
1694
|
+
credentialTestSupported,
|
|
1641
1695
|
onConnect: handleConnect,
|
|
1642
1696
|
onDisconnect: handleDisconnect,
|
|
1697
|
+
onSubmitCredentials: handleSubmitCredentials,
|
|
1698
|
+
onTestCredentials: testCredentials,
|
|
1643
1699
|
listGrants,
|
|
1644
1700
|
addGrant,
|
|
1645
1701
|
revokeGrant
|
|
@@ -1655,8 +1711,12 @@ function ConnectorTile({
|
|
|
1655
1711
|
layout,
|
|
1656
1712
|
shared = false,
|
|
1657
1713
|
grantsSupported,
|
|
1714
|
+
credentialsSupported,
|
|
1715
|
+
credentialTestSupported,
|
|
1658
1716
|
onConnect,
|
|
1659
1717
|
onDisconnect,
|
|
1718
|
+
onSubmitCredentials,
|
|
1719
|
+
onTestCredentials,
|
|
1660
1720
|
listGrants,
|
|
1661
1721
|
addGrant,
|
|
1662
1722
|
revokeGrant
|
|
@@ -1667,6 +1727,11 @@ function ConnectorTile({
|
|
|
1667
1727
|
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1668
1728
|
const [kind, setKind] = (0, import_react9.useState)("personal");
|
|
1669
1729
|
const [sharingOpen, setSharingOpen] = (0, import_react9.useState)(false);
|
|
1730
|
+
const [setupOpen, setSetupOpen] = (0, import_react9.useState)(false);
|
|
1731
|
+
const credentialFields = entry.credential_fields ?? [];
|
|
1732
|
+
const pasteSupported = credentialsSupported && credentialFields.length > 0;
|
|
1733
|
+
const guide = entry.guide ?? null;
|
|
1734
|
+
const canSetUp = !isConnected && (pasteSupported || guide !== null);
|
|
1670
1735
|
const orgPermitted = entry.org_connect?.permitted === true;
|
|
1671
1736
|
const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
|
|
1672
1737
|
const attribution = ownerLabel(entry);
|
|
@@ -1793,18 +1858,55 @@ function ConnectorTile({
|
|
|
1793
1858
|
children: sharingOpen ? "Hide sharing" : "Manage sharing"
|
|
1794
1859
|
}
|
|
1795
1860
|
)
|
|
1796
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1861
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
1862
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1863
|
+
"button",
|
|
1864
|
+
{
|
|
1865
|
+
type: "button",
|
|
1866
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1867
|
+
onClick: () => onConnect(entry, kind),
|
|
1868
|
+
disabled: isConnecting,
|
|
1869
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1870
|
+
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1871
|
+
}
|
|
1872
|
+
),
|
|
1873
|
+
canSetUp && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1874
|
+
"button",
|
|
1875
|
+
{
|
|
1876
|
+
type: "button",
|
|
1877
|
+
"data-testid": `connector-setup-toggle-${entry.slug}`,
|
|
1878
|
+
"aria-expanded": setupOpen,
|
|
1879
|
+
"aria-controls": `connector-setup-panel-${entry.slug}`,
|
|
1880
|
+
onClick: () => setSetupOpen((open) => !open),
|
|
1881
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1882
|
+
children: setupOpen ? "Hide setup" : pasteSupported ? "Use a token instead" : "Setup guide"
|
|
1883
|
+
}
|
|
1884
|
+
)
|
|
1885
|
+
] })
|
|
1807
1886
|
] }),
|
|
1887
|
+
canSetUp && setupOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1888
|
+
"div",
|
|
1889
|
+
{
|
|
1890
|
+
id: `connector-setup-panel-${entry.slug}`,
|
|
1891
|
+
"data-testid": `connector-setup-panel-${entry.slug}`,
|
|
1892
|
+
className: "mt-2 flex flex-col gap-3 rounded-md border border-border bg-foreground/5 p-2",
|
|
1893
|
+
children: [
|
|
1894
|
+
guide && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SetupGuidePanel, { entry, guide, showVerify: !pasteSupported }),
|
|
1895
|
+
pasteSupported && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1896
|
+
CredentialForm,
|
|
1897
|
+
{
|
|
1898
|
+
entry,
|
|
1899
|
+
fields: credentialFields,
|
|
1900
|
+
kind,
|
|
1901
|
+
verify: guide?.verify ?? null,
|
|
1902
|
+
testSupported: credentialTestSupported,
|
|
1903
|
+
onSubmitCredentials,
|
|
1904
|
+
onTestCredentials
|
|
1905
|
+
}
|
|
1906
|
+
)
|
|
1907
|
+
]
|
|
1908
|
+
}
|
|
1909
|
+
),
|
|
1808
1910
|
canManageSharing && sharingOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1809
1911
|
ManageSharing,
|
|
1810
1912
|
{
|
|
@@ -1818,6 +1920,333 @@ function ConnectorTile({
|
|
|
1818
1920
|
}
|
|
1819
1921
|
);
|
|
1820
1922
|
}
|
|
1923
|
+
function groundGuide(guide) {
|
|
1924
|
+
const ungrounded = [];
|
|
1925
|
+
const keep = (text) => {
|
|
1926
|
+
if (!hasUngroundedPlaceholder(text)) return true;
|
|
1927
|
+
ungrounded.push(text);
|
|
1928
|
+
return false;
|
|
1929
|
+
};
|
|
1930
|
+
const prerequisites = guide.prerequisites.filter(keep);
|
|
1931
|
+
const steps = guide.steps.filter(
|
|
1932
|
+
(step) => keep(step.text) && (!step.copyable || keep(step.copyable))
|
|
1933
|
+
);
|
|
1934
|
+
const scopeNote = keep(guide.scope_note) ? guide.scope_note : null;
|
|
1935
|
+
const verify = keep(guide.verify) ? guide.verify : null;
|
|
1936
|
+
return { prerequisites, steps, scopeNote, verify, ungrounded };
|
|
1937
|
+
}
|
|
1938
|
+
function CopyableValue({ value, testId }) {
|
|
1939
|
+
const [copied, setCopied] = (0, import_react9.useState)(false);
|
|
1940
|
+
const timer = (0, import_react9.useRef)(null);
|
|
1941
|
+
(0, import_react9.useEffect)(
|
|
1942
|
+
() => () => {
|
|
1943
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1944
|
+
},
|
|
1945
|
+
[]
|
|
1946
|
+
);
|
|
1947
|
+
const copy = (0, import_react9.useCallback)(async () => {
|
|
1948
|
+
try {
|
|
1949
|
+
if (typeof navigator.clipboard?.writeText !== "function") {
|
|
1950
|
+
throw new Error("clipboard unavailable");
|
|
1951
|
+
}
|
|
1952
|
+
await navigator.clipboard.writeText(value);
|
|
1953
|
+
setCopied(true);
|
|
1954
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1955
|
+
timer.current = setTimeout(() => setCopied(false), 1500);
|
|
1956
|
+
} catch {
|
|
1957
|
+
setCopied(false);
|
|
1958
|
+
}
|
|
1959
|
+
}, [value]);
|
|
1960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mt-1 flex items-center gap-1.5", children: [
|
|
1961
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1962
|
+
"code",
|
|
1963
|
+
{
|
|
1964
|
+
"data-testid": `${testId}-value`,
|
|
1965
|
+
className: "min-w-0 flex-1 truncate rounded bg-foreground/10 px-1.5 py-0.5 text-[11px]",
|
|
1966
|
+
children: value
|
|
1967
|
+
}
|
|
1968
|
+
),
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1970
|
+
"button",
|
|
1971
|
+
{
|
|
1972
|
+
type: "button",
|
|
1973
|
+
"data-testid": testId,
|
|
1974
|
+
onClick: () => void copy(),
|
|
1975
|
+
className: "shrink-0 rounded-md px-1.5 py-0.5 text-[11px] font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1976
|
+
children: copied ? "Copied" : "Copy"
|
|
1977
|
+
}
|
|
1978
|
+
)
|
|
1979
|
+
] });
|
|
1980
|
+
}
|
|
1981
|
+
function SetupGuidePanel({
|
|
1982
|
+
entry,
|
|
1983
|
+
guide,
|
|
1984
|
+
showVerify
|
|
1985
|
+
}) {
|
|
1986
|
+
const { prerequisites, steps, scopeNote, verify, ungrounded } = groundGuide(guide);
|
|
1987
|
+
const leaked = ungrounded.length > 0;
|
|
1988
|
+
(0, import_react9.useEffect)(() => {
|
|
1989
|
+
if (!leaked) return;
|
|
1990
|
+
console.error(
|
|
1991
|
+
`[ConnectorPicker] setup guide for "${entry.slug}" arrived with ungrounded placeholders:`,
|
|
1992
|
+
ungrounded
|
|
1993
|
+
);
|
|
1994
|
+
}, [entry.slug, guide, leaked]);
|
|
1995
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": `connector-guide-${entry.slug}`, className: "flex flex-col gap-2", children: [
|
|
1996
|
+
leaked && isDevEnvironment() && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1997
|
+
"p",
|
|
1998
|
+
{
|
|
1999
|
+
role: "alert",
|
|
2000
|
+
"data-testid": `connector-guide-ungrounded-${entry.slug}`,
|
|
2001
|
+
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1 text-xs text-destructive",
|
|
2002
|
+
children: [
|
|
2003
|
+
"Setup guide arrived with unresolved placeholders and those lines were dropped: ",
|
|
2004
|
+
ungrounded.join(" \xB7 ")
|
|
2005
|
+
]
|
|
2006
|
+
}
|
|
2007
|
+
),
|
|
2008
|
+
prerequisites.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
2009
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h4", { className: "text-xs font-medium", children: "Before you start" }),
|
|
2010
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2011
|
+
"ul",
|
|
2012
|
+
{
|
|
2013
|
+
"data-testid": `connector-guide-prerequisites-${entry.slug}`,
|
|
2014
|
+
className: "mt-1 list-disc pl-4 text-xs text-muted-foreground",
|
|
2015
|
+
children: prerequisites.map((item) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { children: item }, item))
|
|
2016
|
+
}
|
|
2017
|
+
)
|
|
2018
|
+
] }),
|
|
2019
|
+
steps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2020
|
+
"ol",
|
|
2021
|
+
{
|
|
2022
|
+
"data-testid": `connector-guide-steps-${entry.slug}`,
|
|
2023
|
+
className: "list-decimal pl-4 text-xs",
|
|
2024
|
+
children: steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { className: "mt-1 first:mt-0", children: [
|
|
2025
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: step.text }),
|
|
2026
|
+
step.copyable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2027
|
+
CopyableValue,
|
|
2028
|
+
{
|
|
2029
|
+
value: step.copyable,
|
|
2030
|
+
testId: `connector-guide-copy-${entry.slug}-${index}`
|
|
2031
|
+
}
|
|
2032
|
+
)
|
|
2033
|
+
] }, step.text))
|
|
2034
|
+
}
|
|
2035
|
+
),
|
|
2036
|
+
scopeNote && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2037
|
+
"p",
|
|
2038
|
+
{
|
|
2039
|
+
"data-testid": `connector-guide-scope-note-${entry.slug}`,
|
|
2040
|
+
className: "rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs font-medium",
|
|
2041
|
+
children: scopeNote
|
|
2042
|
+
}
|
|
2043
|
+
),
|
|
2044
|
+
showVerify && verify && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2045
|
+
"p",
|
|
2046
|
+
{
|
|
2047
|
+
"data-testid": `connector-guide-verify-${entry.slug}`,
|
|
2048
|
+
className: "text-xs text-muted-foreground",
|
|
2049
|
+
children: verify
|
|
2050
|
+
}
|
|
2051
|
+
)
|
|
2052
|
+
] });
|
|
2053
|
+
}
|
|
2054
|
+
function CredentialForm({
|
|
2055
|
+
entry,
|
|
2056
|
+
fields,
|
|
2057
|
+
kind,
|
|
2058
|
+
verify,
|
|
2059
|
+
testSupported,
|
|
2060
|
+
onSubmitCredentials,
|
|
2061
|
+
onTestCredentials
|
|
2062
|
+
}) {
|
|
2063
|
+
const [values, setValues] = (0, import_react9.useState)(() => {
|
|
2064
|
+
const initial = {};
|
|
2065
|
+
for (const field of fields) {
|
|
2066
|
+
if (field.defaultValue) initial[field.key] = field.defaultValue;
|
|
2067
|
+
}
|
|
2068
|
+
return initial;
|
|
2069
|
+
});
|
|
2070
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
2071
|
+
const [testing, setTesting] = (0, import_react9.useState)(false);
|
|
2072
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
2073
|
+
const [result, setResult] = (0, import_react9.useState)(null);
|
|
2074
|
+
const entryRef = (0, import_react9.useRef)(entry);
|
|
2075
|
+
entryRef.current = entry;
|
|
2076
|
+
const updateField = (0, import_react9.useCallback)((key, value) => {
|
|
2077
|
+
setValues((prev) => ({ ...prev, [key]: value }));
|
|
2078
|
+
setError(null);
|
|
2079
|
+
setResult(null);
|
|
2080
|
+
}, []);
|
|
2081
|
+
const missingRequired = fields.filter((field) => field.required !== false).some((field) => !values[field.key]?.trim());
|
|
2082
|
+
const failedTest = result?.valid === false;
|
|
2083
|
+
const untested = testSupported && result === null;
|
|
2084
|
+
const fieldErrors = result?.field_errors ?? null;
|
|
2085
|
+
const handleTest = (0, import_react9.useCallback)(async () => {
|
|
2086
|
+
setTesting(true);
|
|
2087
|
+
setError(null);
|
|
2088
|
+
setResult(null);
|
|
2089
|
+
try {
|
|
2090
|
+
setResult(await onTestCredentials(entryRef.current, values));
|
|
2091
|
+
} catch (err) {
|
|
2092
|
+
setError(errorMessageFrom(err, DEFAULT_CREDENTIAL_TEST_ERROR));
|
|
2093
|
+
} finally {
|
|
2094
|
+
setTesting(false);
|
|
2095
|
+
}
|
|
2096
|
+
}, [onTestCredentials, values]);
|
|
2097
|
+
const handleSubmit = (0, import_react9.useCallback)(async () => {
|
|
2098
|
+
setBusy(true);
|
|
2099
|
+
setError(null);
|
|
2100
|
+
try {
|
|
2101
|
+
await onSubmitCredentials(entryRef.current, values, kind);
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
setError(errorMessageFrom(err, DEFAULT_CREDENTIAL_ERROR));
|
|
2104
|
+
} finally {
|
|
2105
|
+
setBusy(false);
|
|
2106
|
+
}
|
|
2107
|
+
}, [kind, onSubmitCredentials, values]);
|
|
2108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2109
|
+
"form",
|
|
2110
|
+
{
|
|
2111
|
+
"data-testid": `connector-credential-form-${entry.slug}`,
|
|
2112
|
+
onSubmit: (event) => {
|
|
2113
|
+
event.preventDefault();
|
|
2114
|
+
if (missingRequired || busy || testing || failedTest || untested) return;
|
|
2115
|
+
void handleSubmit();
|
|
2116
|
+
},
|
|
2117
|
+
className: "flex flex-col gap-2",
|
|
2118
|
+
children: [
|
|
2119
|
+
fields.map((field) => {
|
|
2120
|
+
const inputId = `connector-credential-${entry.slug}-${field.key}`;
|
|
2121
|
+
const fieldError = fieldErrors?.[field.key] ?? null;
|
|
2122
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { htmlFor: inputId, className: "text-xs font-medium", children: [
|
|
2124
|
+
field.label,
|
|
2125
|
+
field.required !== false && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })
|
|
2126
|
+
] }),
|
|
2127
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2128
|
+
"input",
|
|
2129
|
+
{
|
|
2130
|
+
id: inputId,
|
|
2131
|
+
"data-testid": `connector-credential-input-${entry.slug}-${field.key}`,
|
|
2132
|
+
type: field.type ?? "text",
|
|
2133
|
+
value: values[field.key] ?? "",
|
|
2134
|
+
onChange: (event) => updateField(field.key, event.target.value),
|
|
2135
|
+
placeholder: field.placeholder ?? void 0,
|
|
2136
|
+
required: field.required !== false,
|
|
2137
|
+
disabled: busy || testing,
|
|
2138
|
+
autoComplete: "off",
|
|
2139
|
+
"aria-invalid": fieldError ? true : void 0,
|
|
2140
|
+
"aria-describedby": fieldError ? `${inputId}-error` : void 0,
|
|
2141
|
+
className: cn(
|
|
2142
|
+
"rounded-md border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
2143
|
+
fieldError ? "border-destructive" : "border-border"
|
|
2144
|
+
)
|
|
2145
|
+
}
|
|
2146
|
+
),
|
|
2147
|
+
fieldError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2148
|
+
"p",
|
|
2149
|
+
{
|
|
2150
|
+
id: `${inputId}-error`,
|
|
2151
|
+
"data-testid": `connector-credential-field-error-${entry.slug}-${field.key}`,
|
|
2152
|
+
className: "text-xs text-destructive",
|
|
2153
|
+
children: fieldError
|
|
2154
|
+
}
|
|
2155
|
+
)
|
|
2156
|
+
] }, field.key);
|
|
2157
|
+
}),
|
|
2158
|
+
verify && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2159
|
+
"p",
|
|
2160
|
+
{
|
|
2161
|
+
"data-testid": `connector-guide-verify-${entry.slug}`,
|
|
2162
|
+
className: "text-xs text-muted-foreground",
|
|
2163
|
+
children: verify
|
|
2164
|
+
}
|
|
2165
|
+
),
|
|
2166
|
+
result && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CredentialTestResult, { entry, result }),
|
|
2167
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2168
|
+
"p",
|
|
2169
|
+
{
|
|
2170
|
+
role: "alert",
|
|
2171
|
+
"data-testid": `connector-credential-error-${entry.slug}`,
|
|
2172
|
+
className: "text-xs text-destructive",
|
|
2173
|
+
children: error
|
|
2174
|
+
}
|
|
2175
|
+
),
|
|
2176
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-end gap-1.5", children: [
|
|
2177
|
+
testSupported && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2178
|
+
"button",
|
|
2179
|
+
{
|
|
2180
|
+
type: "button",
|
|
2181
|
+
"data-testid": `connector-credential-test-${entry.slug}`,
|
|
2182
|
+
onClick: () => void handleTest(),
|
|
2183
|
+
disabled: missingRequired || busy || testing,
|
|
2184
|
+
className: "rounded-md border border-border px-2.5 py-1 text-xs font-medium disabled:opacity-50",
|
|
2185
|
+
children: testing ? "Testing\u2026" : "Test connection"
|
|
2186
|
+
}
|
|
2187
|
+
),
|
|
2188
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2189
|
+
"button",
|
|
2190
|
+
{
|
|
2191
|
+
type: "submit",
|
|
2192
|
+
"data-testid": `connector-credential-save-${entry.slug}`,
|
|
2193
|
+
disabled: missingRequired || busy || testing || failedTest || untested,
|
|
2194
|
+
title: failedTest ? "That credential failed its own check \u2014 fix it and test again." : untested ? "Test the credential before saving it." : void 0,
|
|
2195
|
+
className: "rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
2196
|
+
children: busy ? "Saving\u2026" : "Save credential"
|
|
2197
|
+
}
|
|
2198
|
+
)
|
|
2199
|
+
] })
|
|
2200
|
+
]
|
|
2201
|
+
}
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
function CredentialTestResult({
|
|
2205
|
+
entry,
|
|
2206
|
+
result
|
|
2207
|
+
}) {
|
|
2208
|
+
if (result.valid === true) {
|
|
2209
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2210
|
+
"div",
|
|
2211
|
+
{
|
|
2212
|
+
role: "status",
|
|
2213
|
+
"data-testid": `connector-credential-test-pass-${entry.slug}`,
|
|
2214
|
+
className: "rounded-md border border-primary/30 bg-primary/5 px-2 py-1 text-xs",
|
|
2215
|
+
children: [
|
|
2216
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: result.account_name ? `Verified \u2014 connected as ${result.account_name}.` : "Verified \u2014 these credentials work." }),
|
|
2217
|
+
result.permissions_note && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2218
|
+
"p",
|
|
2219
|
+
{
|
|
2220
|
+
"data-testid": `connector-credential-permissions-${entry.slug}`,
|
|
2221
|
+
className: "mt-0.5 font-medium",
|
|
2222
|
+
children: result.permissions_note
|
|
2223
|
+
}
|
|
2224
|
+
)
|
|
2225
|
+
]
|
|
2226
|
+
}
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
if (result.valid === false) {
|
|
2230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2231
|
+
"p",
|
|
2232
|
+
{
|
|
2233
|
+
role: "alert",
|
|
2234
|
+
"data-testid": `connector-credential-test-fail-${entry.slug}`,
|
|
2235
|
+
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1 text-xs text-destructive",
|
|
2236
|
+
children: result.error || "That credential didn't pass its check."
|
|
2237
|
+
}
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2241
|
+
"p",
|
|
2242
|
+
{
|
|
2243
|
+
role: "status",
|
|
2244
|
+
"data-testid": `connector-credential-test-unverified-${entry.slug}`,
|
|
2245
|
+
className: "rounded-md border border-border px-2 py-1 text-xs text-muted-foreground",
|
|
2246
|
+
children: UNVERIFIABLE_CREDENTIAL_NOTE
|
|
2247
|
+
}
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
1821
2250
|
function ManageSharing({
|
|
1822
2251
|
entry,
|
|
1823
2252
|
listGrants,
|