@meetreeve/ui 0.9.0 → 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/index.js CHANGED
@@ -1427,7 +1427,17 @@ var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
1427
1427
  var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
1428
1428
  var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
1429
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.";
1430
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
+ }
1431
1441
  function capabilityLabel(capability) {
1432
1442
  if (capability === "read") return "Read only";
1433
1443
  if (capability === "write") return "Write only";
@@ -1464,6 +1474,8 @@ function ConnectorPicker({
1464
1474
  const [loadError, setLoadError] = (0, import_react9.useState)(null);
1465
1475
  const [uiState, setUiState] = (0, import_react9.useState)({});
1466
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";
1467
1479
  const clientRef = (0, import_react9.useRef)(client);
1468
1480
  clientRef.current = client;
1469
1481
  const purposeRef = (0, import_react9.useRef)(purpose);
@@ -1547,6 +1559,38 @@ function ConnectorPicker({
1547
1559
  },
1548
1560
  [load]
1549
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
+ );
1550
1594
  const handleDisconnect = (0, import_react9.useCallback)(
1551
1595
  async (entry) => {
1552
1596
  setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "disconnecting" } }));
@@ -1619,8 +1663,12 @@ function ConnectorPicker({
1619
1663
  ui: uiState[entry.slug],
1620
1664
  layout,
1621
1665
  grantsSupported,
1666
+ credentialsSupported,
1667
+ credentialTestSupported,
1622
1668
  onConnect: handleConnect,
1623
1669
  onDisconnect: handleDisconnect,
1670
+ onSubmitCredentials: handleSubmitCredentials,
1671
+ onTestCredentials: testCredentials,
1624
1672
  listGrants,
1625
1673
  addGrant,
1626
1674
  revokeGrant
@@ -1638,8 +1686,12 @@ function ConnectorPicker({
1638
1686
  layout,
1639
1687
  shared: true,
1640
1688
  grantsSupported,
1689
+ credentialsSupported,
1690
+ credentialTestSupported,
1641
1691
  onConnect: handleConnect,
1642
1692
  onDisconnect: handleDisconnect,
1693
+ onSubmitCredentials: handleSubmitCredentials,
1694
+ onTestCredentials: testCredentials,
1643
1695
  listGrants,
1644
1696
  addGrant,
1645
1697
  revokeGrant
@@ -1655,8 +1707,12 @@ function ConnectorTile({
1655
1707
  layout,
1656
1708
  shared = false,
1657
1709
  grantsSupported,
1710
+ credentialsSupported,
1711
+ credentialTestSupported,
1658
1712
  onConnect,
1659
1713
  onDisconnect,
1714
+ onSubmitCredentials,
1715
+ onTestCredentials,
1660
1716
  listGrants,
1661
1717
  addGrant,
1662
1718
  revokeGrant
@@ -1667,6 +1723,11 @@ function ConnectorTile({
1667
1723
  const errorMessage = ui?.kind === "error" ? ui.message : null;
1668
1724
  const [kind, setKind] = (0, import_react9.useState)("personal");
1669
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);
1670
1731
  const orgPermitted = entry.org_connect?.permitted === true;
1671
1732
  const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
1672
1733
  const attribution = ownerLabel(entry);
@@ -1793,18 +1854,55 @@ function ConnectorTile({
1793
1854
  children: sharingOpen ? "Hide sharing" : "Manage sharing"
1794
1855
  }
1795
1856
  )
1796
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1797
- "button",
1798
- {
1799
- type: "button",
1800
- "data-testid": `connector-connect-${entry.slug}`,
1801
- onClick: () => onConnect(entry, kind),
1802
- disabled: isConnecting,
1803
- className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
1804
- children: isConnecting ? "Connecting\u2026" : "Connect"
1805
- }
1806
- )
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
+ ] })
1807
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
+ ),
1808
1906
  canManageSharing && sharingOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1809
1907
  ManageSharing,
1810
1908
  {
@@ -1818,6 +1916,333 @@ function ConnectorTile({
1818
1916
  }
1819
1917
  );
1820
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",
2175
+ {
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"
2182
+ }
2183
+ ),
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
+ }
1821
2246
  function ManageSharing({
1822
2247
  entry,
1823
2248
  listGrants,