nucleus-core-ts 0.9.805 → 0.9.807

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.
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useLabels } from '../text/context';
3
4
  import { integrationsPageTheme } from '../theme';
4
5
  import { AUTH_TYPE_OPTIONS, authTypeHint, toAuthType } from './authOptions';
5
6
  import { ConnectionOauthFields } from './ConnectionOauthFields';
@@ -16,15 +17,16 @@ import { Field } from './Primitives';
16
17
  * because they are written and never read back.
17
18
  */ const theme = integrationsPageTheme;
18
19
  export function ConnectionAuthFields(props) {
20
+ const labels = useLabels();
19
21
  const { draft, disabled, onChange } = props;
20
22
  return /*#__PURE__*/ _jsxs("section", {
21
- "aria-label": "Authentication",
23
+ "aria-label": labels.auth.sectionLabel,
22
24
  className: "flex flex-col gap-3",
23
25
  children: [
24
26
  /*#__PURE__*/ _jsx(Field, {
25
27
  hint: authTypeHint(draft.authType),
26
28
  htmlFor: "connection-auth-type",
27
- label: "Auth type",
29
+ label: labels.auth.type,
28
30
  children: /*#__PURE__*/ _jsx("select", {
29
31
  "aria-describedby": describedBy('connection-auth-type', true, false),
30
32
  className: theme.field.input,
@@ -44,9 +46,9 @@ export function ConnectionAuthFields(props) {
44
46
  })
45
47
  }),
46
48
  usesHeaderName(draft.authType) ? /*#__PURE__*/ _jsx(Field, {
47
- hint: "Empty means Authorization. Set it when the service reads the credential from a header of its own.",
49
+ hint: labels.auth.headerNameHint,
48
50
  htmlFor: "connection-auth-header",
49
- label: "Header name",
51
+ label: labels.auth.headerName,
50
52
  children: /*#__PURE__*/ _jsx("input", {
51
53
  "aria-describedby": describedBy('connection-auth-header', true, false),
52
54
  className: theme.field.input,
@@ -55,15 +57,15 @@ export function ConnectionAuthFields(props) {
55
57
  onChange: (event)=>onChange({
56
58
  authHeaderName: event.target.value
57
59
  }),
58
- placeholder: "X-Api-Key",
60
+ placeholder: labels.auth.headerNamePlaceholder,
59
61
  type: "text",
60
62
  value: draft.authHeaderName
61
63
  })
62
64
  }) : null,
63
65
  usesQueryParam(draft.authType) ? /*#__PURE__*/ _jsx(Field, {
64
- hint: "Use this instead of a header when the key belongs in the query string. Fill in one or the other, not both.",
66
+ hint: labels.auth.queryParamHint,
65
67
  htmlFor: "connection-auth-query",
66
- label: "Query parameter",
68
+ label: labels.auth.queryParam,
67
69
  children: /*#__PURE__*/ _jsx("input", {
68
70
  "aria-describedby": describedBy('connection-auth-query', true, false),
69
71
  className: theme.field.input,
@@ -72,7 +74,7 @@ export function ConnectionAuthFields(props) {
72
74
  onChange: (event)=>onChange({
73
75
  authQueryParam: event.target.value
74
76
  }),
75
- placeholder: "api_key",
77
+ placeholder: labels.auth.queryParamPlaceholder,
76
78
  type: "text",
77
79
  value: draft.authQueryParam
78
80
  })
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useLabels } from '../text/context';
3
4
  import { integrationsPageTheme } from '../theme';
4
5
  import { CLIENT_AUTH_OPTIONS, GRANT_TYPE_OPTIONS, toClientAuth, toGrantType } from './authOptions';
5
6
  import { usesOauth } from './connectionDraft';
@@ -13,14 +14,15 @@ import { Field } from './Primitives';
13
14
  * place instead of at every field.
14
15
  */ const theme = integrationsPageTheme;
15
16
  export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
17
+ const labels = useLabels();
16
18
  if (!usesOauth(draft.authType)) return null;
17
19
  return /*#__PURE__*/ _jsxs(_Fragment, {
18
20
  children: [
19
21
  /*#__PURE__*/ _jsx(Field, {
20
22
  error: errors.tokenUrl,
21
- hint: "The full URL the token is fetched from. Same rule as the base URL: any scheme, http included.",
23
+ hint: labels.auth.tokenUrlHint,
22
24
  htmlFor: "connection-token-url",
23
- label: "Token URL",
25
+ label: labels.auth.tokenUrl,
24
26
  children: /*#__PURE__*/ _jsx("input", {
25
27
  "aria-describedby": describedBy('connection-token-url', true, Boolean(errors.tokenUrl)),
26
28
  className: theme.field.input,
@@ -29,15 +31,15 @@ export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
29
31
  onChange: (event)=>onChange({
30
32
  tokenUrl: event.target.value
31
33
  }),
32
- placeholder: "https://api.example.com/oauth/token",
34
+ placeholder: labels.auth.tokenUrlPlaceholder,
33
35
  type: "text",
34
36
  value: draft.tokenUrl
35
37
  })
36
38
  }),
37
39
  /*#__PURE__*/ _jsx(Field, {
38
- hint: "Client credentials calls as the application. Password calls as a named user and needs that user's name and password.",
40
+ hint: labels.auth.grantTypeHint,
39
41
  htmlFor: "connection-grant-type",
40
- label: "Grant type",
42
+ label: labels.auth.grantType,
41
43
  children: /*#__PURE__*/ _jsx("select", {
42
44
  "aria-describedby": describedBy('connection-grant-type', true, false),
43
45
  className: theme.field.input,
@@ -58,7 +60,7 @@ export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
58
60
  }),
59
61
  /*#__PURE__*/ _jsx(Field, {
60
62
  htmlFor: "connection-client-id",
61
- label: "Client ID",
63
+ label: labels.auth.clientId,
62
64
  children: /*#__PURE__*/ _jsx("input", {
63
65
  className: theme.field.input,
64
66
  disabled: disabled,
@@ -66,14 +68,14 @@ export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
66
68
  onChange: (event)=>onChange({
67
69
  oauthClientId: event.target.value
68
70
  }),
69
- placeholder: "integration-client",
71
+ placeholder: labels.auth.clientIdPlaceholder,
70
72
  type: "text",
71
73
  value: draft.oauthClientId
72
74
  })
73
75
  }),
74
76
  draft.oauthGrantType === 'password' ? /*#__PURE__*/ _jsx(Field, {
75
77
  htmlFor: "connection-oauth-username",
76
- label: "User name",
78
+ label: labels.auth.username,
77
79
  children: /*#__PURE__*/ _jsx("input", {
78
80
  autoComplete: "off",
79
81
  className: theme.field.input,
@@ -82,15 +84,15 @@ export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
82
84
  onChange: (event)=>onChange({
83
85
  oauthUsername: event.target.value
84
86
  }),
85
- placeholder: "service-account",
87
+ placeholder: labels.auth.usernamePlaceholder,
86
88
  type: "text",
87
89
  value: draft.oauthUsername
88
90
  })
89
91
  }) : null,
90
92
  /*#__PURE__*/ _jsx(Field, {
91
- hint: "Space-separated. Leave it empty to take whatever the token endpoint grants by default.",
93
+ hint: labels.auth.scopeHint,
92
94
  htmlFor: "connection-scope",
93
- label: "Scope",
95
+ label: labels.auth.scope,
94
96
  children: /*#__PURE__*/ _jsx("input", {
95
97
  "aria-describedby": describedBy('connection-scope', true, false),
96
98
  className: theme.field.input,
@@ -99,7 +101,7 @@ export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
99
101
  onChange: (event)=>onChange({
100
102
  oauthScope: event.target.value
101
103
  }),
102
- placeholder: "read:records",
104
+ placeholder: labels.auth.scopePlaceholder,
103
105
  type: "text",
104
106
  value: draft.oauthScope
105
107
  })
@@ -107,7 +109,7 @@ export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
107
109
  /*#__PURE__*/ _jsx(Field, {
108
110
  hint: "Where the client ID and secret go on the token request. A server accepts one or the other; if the token call comes back with an invalid-client error, try the other.",
109
111
  htmlFor: "connection-client-auth",
110
- label: "Client authentication",
112
+ label: labels.auth.clientAuth,
111
113
  children: /*#__PURE__*/ _jsx("select", {
112
114
  "aria-describedby": describedBy('connection-client-auth', true, false),
113
115
  className: theme.field.input,
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useEffectEvent, useState } from 'react';
4
4
  import { cn } from '../../../utils/cn';
5
+ import { useLabels } from '../text/context';
5
6
  import { integrationsPageTheme } from '../theme';
6
7
  import { errorText } from './formSupport';
7
8
  import { Badge, Notice } from './Primitives';
@@ -15,6 +16,7 @@ import { CardSkeleton } from './Skeletons';
15
16
  * sends the operator back to guessing.
16
17
  */ const theme = integrationsPageTheme;
17
18
  export function ConnectionTest({ actions, source, loading = false }) {
19
+ const labels = useLabels();
18
20
  const [testing, setTesting] = useState(false);
19
21
  const [result, setResult] = useState(null);
20
22
  // A result describes the connection it was run against. Leaving it up under
@@ -45,7 +47,7 @@ export function ConnectionTest({ actions, source, loading = false }) {
45
47
  };
46
48
  if (loading) return /*#__PURE__*/ _jsx(CardSkeleton, {});
47
49
  return /*#__PURE__*/ _jsxs("section", {
48
- "aria-label": "Connection test",
50
+ "aria-label": labels.auth.testHeading,
49
51
  className: theme.card.wrapper,
50
52
  children: [
51
53
  /*#__PURE__*/ _jsxs("div", {
@@ -53,7 +55,7 @@ export function ConnectionTest({ actions, source, loading = false }) {
53
55
  children: [
54
56
  /*#__PURE__*/ _jsx("h2", {
55
57
  className: theme.card.title,
56
- children: "Connection test"
58
+ children: labels.auth.testHeading
57
59
  }),
58
60
  result ? /*#__PURE__*/ _jsx(Badge, {
59
61
  tone: result.ok ? 'success' : 'danger',
@@ -76,13 +78,13 @@ export function ConnectionTest({ actions, source, loading = false }) {
76
78
  children: "Save the connection first — there is nothing to call yet."
77
79
  }),
78
80
  result?.ok ? /*#__PURE__*/ _jsxs(Notice, {
79
- title: "Connection succeeded",
81
+ title: labels.auth.testOkTitle,
80
82
  tone: "info",
81
83
  children: [
82
84
  /*#__PURE__*/ _jsx("p", {
83
85
  children: /*#__PURE__*/ _jsx(Badge, {
84
86
  tone: "success",
85
- children: "Reachable"
87
+ children: labels.auth.testOkBadge
86
88
  })
87
89
  }),
88
90
  result.detail ? /*#__PURE__*/ _jsx("p", {
@@ -92,7 +94,7 @@ export function ConnectionTest({ actions, source, loading = false }) {
92
94
  ]
93
95
  }) : null,
94
96
  result && !result.ok ? /*#__PURE__*/ _jsx(Notice, {
95
- title: "Connection failed",
97
+ title: labels.auth.testFailedTitle,
96
98
  tone: "danger",
97
99
  children: /*#__PURE__*/ _jsx("p", {
98
100
  className: cn(theme.notice.body, 'whitespace-pre-wrap break-words font-mono'),
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useEffectEvent, useState } from 'react';
4
4
  import { useIntegrationsStore } from '../store';
5
+ import { useLabels } from '../text/context';
5
6
  import { integrationsPageTheme } from '../theme';
6
7
  import { CredentialSlotRow } from './CredentialSlotRow';
7
8
  import { hintFromResult, patchFor, slotsForSource, storedHint } from './credentialSlots';
@@ -18,6 +19,7 @@ import { CardSkeleton } from './Skeletons';
18
19
  * button per field.
19
20
  */ const theme = integrationsPageTheme;
20
21
  export function CredentialFields({ actions, source, loading = false }) {
22
+ const labels = useLabels();
21
23
  const store = useIntegrationsStore();
22
24
  const [values, setValues] = useState({});
23
25
  const [hints, setHints] = useState({});
@@ -39,10 +41,10 @@ export function CredentialFields({ actions, source, loading = false }) {
39
41
  if (loading) return /*#__PURE__*/ _jsx(CardSkeleton, {});
40
42
  if (!source) {
41
43
  return /*#__PURE__*/ _jsx(Notice, {
42
- title: "Save the connection first",
44
+ title: labels.auth.saveFirstTitle,
43
45
  tone: "info",
44
46
  children: /*#__PURE__*/ _jsx("p", {
45
- children: "Secrets are stored against a saved connection, so there is somewhere to put them."
47
+ children: labels.auth.saveFirstBody
46
48
  })
47
49
  });
48
50
  }
@@ -114,21 +116,21 @@ export function CredentialFields({ actions, source, loading = false }) {
114
116
  });
115
117
  };
116
118
  return /*#__PURE__*/ _jsxs("section", {
117
- "aria-label": "Credentials",
119
+ "aria-label": labels.auth.credentialsHeading,
118
120
  className: theme.card.wrapper,
119
121
  children: [
120
122
  /*#__PURE__*/ _jsx("div", {
121
123
  className: theme.card.header,
122
124
  children: /*#__PURE__*/ _jsx("h2", {
123
125
  className: theme.card.title,
124
- children: "Credentials"
126
+ children: labels.auth.credentialsHeading
125
127
  })
126
128
  }),
127
129
  /*#__PURE__*/ _jsxs("div", {
128
130
  className: theme.card.body,
129
131
  children: [
130
132
  /*#__PURE__*/ _jsx(Notice, {
131
- title: "Written once, never read back",
133
+ title: labels.auth.writeOnlyTitle,
132
134
  tone: "info",
133
135
  children: /*#__PURE__*/ _jsx("p", {
134
136
  children: "A value typed here is stored and from then on only ever sent to the service. It is not returned to this screen, or to any other, no matter who asks — all that comes back is a masked hint. Leave a box empty to keep whatever is already stored."
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useEffectEvent, useState } from 'react';
4
4
  import { useIntegrationsStore } from '../store';
5
+ import { useLabels } from '../text/context';
5
6
  import { integrationsPageTheme } from '../theme';
6
7
  import { EndpointFormFields } from './EndpointFormFields';
7
8
  import { formatJsonObject, parseJsonObject } from './jsonObjectField';
@@ -17,6 +18,7 @@ import { pathParamNames } from './pathParams';
17
18
  * a list page arriving in the background.
18
19
  */ const theme = integrationsPageTheme;
19
20
  export function EndpointForm({ sourceId, endpoint, actions, onSaved, onDeleted, onCancel }) {
21
+ const labels = useLabels();
20
22
  const store = useIntegrationsStore();
21
23
  const [saving, setSaving] = useState(false);
22
24
  const [deleting, setDeleting] = useState(false);
@@ -123,7 +125,7 @@ export function EndpointForm({ sourceId, endpoint, actions, onSaved, onDeleted,
123
125
  className: theme.card.header,
124
126
  children: /*#__PURE__*/ _jsx("h3", {
125
127
  className: theme.card.title,
126
- children: endpoint ? 'Edit endpoint' : 'New endpoint'
128
+ children: endpoint ? labels.endpoints.editTitle : labels.endpoints.newTitle
127
129
  })
128
130
  }),
129
131
  /*#__PURE__*/ _jsxs("div", {
@@ -163,20 +165,20 @@ export function EndpointForm({ sourceId, endpoint, actions, onSaved, onDeleted,
163
165
  disabled: busy,
164
166
  onClick: remove,
165
167
  type: "button",
166
- children: deleting ? 'Deleting…' : 'Delete'
168
+ children: deleting ? labels.common.deleting : labels.common.delete
167
169
  }) : null,
168
170
  /*#__PURE__*/ _jsx("button", {
169
171
  className: theme.button.secondary,
170
172
  disabled: busy,
171
173
  onClick: onCancel,
172
174
  type: "button",
173
- children: "Cancel"
175
+ children: labels.common.cancel
174
176
  }),
175
177
  /*#__PURE__*/ _jsx("button", {
176
178
  className: theme.button.primary,
177
179
  disabled: busy || !canSave,
178
180
  type: "submit",
179
- children: saving ? 'Saving…' : 'Save endpoint'
181
+ children: saving ? labels.common.saving : labels.endpoints.save
180
182
  })
181
183
  ]
182
184
  })
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useId } from 'react';
3
4
  import { cn } from '../../../utils/cn';
4
5
  import { useLabels } from '../text/context';
5
6
  import { integrationsPageTheme } from '../theme';
@@ -20,6 +21,14 @@ const METHODS = [
20
21
  ];
21
22
  export function EndpointFormFields({ draft, disabled, errors, onChange }) {
22
23
  const labels = useLabels();
24
+ /**
25
+ * Unique per instance.
26
+ *
27
+ * These ids were literals — `endpoint-tag`, `endpoint-name` — which is fine
28
+ * for one form on a page and wrong the moment there are two: every `<label
29
+ * htmlFor>` binds to whichever rendered first, so clicking a label focuses the
30
+ * OTHER record's field. `useId` makes that structurally impossible.
31
+ */ const uid = useId();
23
32
  return /*#__PURE__*/ _jsxs(_Fragment, {
24
33
  children: [
25
34
  /*#__PURE__*/ _jsxs("div", {
@@ -27,13 +36,13 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
27
36
  children: [
28
37
  /*#__PURE__*/ _jsx(Field, {
29
38
  hint: labels.endpoints.tagHint,
30
- htmlFor: "endpoint-tag",
39
+ htmlFor: `${uid}-tag`,
31
40
  label: labels.endpoints.tag,
32
41
  children: /*#__PURE__*/ _jsx("input", {
33
- "aria-describedby": "endpoint-tag-hint",
42
+ "aria-describedby": `${uid}-tag-hint`,
34
43
  className: theme.field.input,
35
44
  disabled: disabled,
36
- id: "endpoint-tag",
45
+ id: `${uid}-tag`,
37
46
  onChange: (event)=>onChange({
38
47
  tag: event.target.value
39
48
  }),
@@ -43,12 +52,12 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
43
52
  })
44
53
  }),
45
54
  /*#__PURE__*/ _jsx(Field, {
46
- htmlFor: "endpoint-name",
55
+ htmlFor: `${uid}-name`,
47
56
  label: labels.endpoints.name,
48
57
  children: /*#__PURE__*/ _jsx("input", {
49
58
  className: theme.field.input,
50
59
  disabled: disabled,
51
- id: "endpoint-name",
60
+ id: `${uid}-name`,
52
61
  onChange: (event)=>onChange({
53
62
  name: event.target.value
54
63
  }),
@@ -65,12 +74,12 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
65
74
  /*#__PURE__*/ _jsx("div", {
66
75
  className: "sm:w-32",
67
76
  children: /*#__PURE__*/ _jsx(Field, {
68
- htmlFor: "endpoint-method",
77
+ htmlFor: `${uid}-method`,
69
78
  label: labels.endpoints.method,
70
79
  children: /*#__PURE__*/ _jsx("select", {
71
80
  className: theme.field.input,
72
81
  disabled: disabled,
73
- id: "endpoint-method",
82
+ id: `${uid}-method`,
74
83
  onChange: (event)=>{
75
84
  for (const method of METHODS){
76
85
  if (method === event.target.value) onChange({
@@ -90,13 +99,13 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
90
99
  className: "min-w-0 flex-1",
91
100
  children: /*#__PURE__*/ _jsx(Field, {
92
101
  hint: labels.endpoints.pathHint,
93
- htmlFor: "endpoint-path",
102
+ htmlFor: `${uid}-path`,
94
103
  label: labels.endpoints.path,
95
104
  children: /*#__PURE__*/ _jsx("input", {
96
- "aria-describedby": "endpoint-path-hint",
105
+ "aria-describedby": `${uid}-path-hint`,
97
106
  className: theme.field.input,
98
107
  disabled: disabled,
99
- id: "endpoint-path",
108
+ id: `${uid}-path`,
100
109
  onChange: (event)=>onChange({
101
110
  path: event.target.value
102
111
  }),
@@ -110,13 +119,13 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
110
119
  }),
111
120
  /*#__PURE__*/ _jsx(Field, {
112
121
  hint: labels.endpoints.rootPathHint,
113
- htmlFor: "endpoint-root",
122
+ htmlFor: `${uid}-root`,
114
123
  label: labels.endpoints.rootPath,
115
124
  children: /*#__PURE__*/ _jsx("input", {
116
- "aria-describedby": "endpoint-root-hint",
125
+ "aria-describedby": `${uid}-root-hint`,
117
126
  className: theme.field.input,
118
127
  disabled: disabled,
119
- id: "endpoint-root",
128
+ id: `${uid}-root`,
120
129
  onChange: (event)=>onChange({
121
130
  responseRootPath: event.target.value
122
131
  }),
@@ -128,13 +137,13 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
128
137
  /*#__PURE__*/ _jsx(Field, {
129
138
  error: errors?.queryParams,
130
139
  hint: labels.endpoints.queryParamsHint,
131
- htmlFor: "endpoint-query",
140
+ htmlFor: `${uid}-query`,
132
141
  label: labels.endpoints.queryParams,
133
142
  children: /*#__PURE__*/ _jsx("textarea", {
134
- "aria-describedby": describedBy('endpoint-query', true, Boolean(errors?.queryParams)),
143
+ "aria-describedby": describedBy(`${uid}-query`, true, Boolean(errors?.queryParams)),
135
144
  className: cn(theme.field.input, 'h-auto min-h-16 py-2 font-mono text-xs'),
136
145
  disabled: disabled,
137
- id: "endpoint-query",
146
+ id: `${uid}-query`,
138
147
  onChange: (event)=>onChange({
139
148
  queryParams: event.target.value
140
149
  }),
@@ -146,13 +155,13 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
146
155
  draft.method === 'GET' ? null : /*#__PURE__*/ _jsx(Field, {
147
156
  error: errors?.requestBody,
148
157
  hint: labels.endpoints.requestBodyHint,
149
- htmlFor: "endpoint-body",
158
+ htmlFor: `${uid}-body`,
150
159
  label: labels.endpoints.requestBody,
151
160
  children: /*#__PURE__*/ _jsx("textarea", {
152
- "aria-describedby": describedBy('endpoint-body', true, Boolean(errors?.requestBody)),
161
+ "aria-describedby": describedBy(`${uid}-body`, true, Boolean(errors?.requestBody)),
153
162
  className: cn(theme.field.input, 'h-auto min-h-20 py-2 font-mono text-xs'),
154
163
  disabled: disabled,
155
- id: "endpoint-body",
164
+ id: `${uid}-body`,
156
165
  onChange: (event)=>onChange({
157
166
  requestBody: event.target.value
158
167
  }),
@@ -162,12 +171,12 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
162
171
  })
163
172
  }),
164
173
  /*#__PURE__*/ _jsx(Field, {
165
- htmlFor: "endpoint-description",
174
+ htmlFor: `${uid}-description`,
166
175
  label: labels.endpoints.description,
167
176
  children: /*#__PURE__*/ _jsx("textarea", {
168
177
  className: cn(theme.field.input, 'h-auto min-h-20 py-2'),
169
178
  disabled: disabled,
170
- id: "endpoint-description",
179
+ id: `${uid}-description`,
171
180
  onChange: (event)=>onChange({
172
181
  description: event.target.value
173
182
  }),
@@ -178,13 +187,13 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
178
187
  }),
179
188
  /*#__PURE__*/ _jsxs("label", {
180
189
  className: "flex items-center gap-2",
181
- htmlFor: "endpoint-enabled",
190
+ htmlFor: `${uid}-enabled`,
182
191
  children: [
183
192
  /*#__PURE__*/ _jsx("input", {
184
193
  checked: draft.enabled,
185
194
  className: "size-4 shrink-0 accent-sky-600 dark:accent-sky-500",
186
195
  disabled: disabled,
187
- id: "endpoint-enabled",
196
+ id: `${uid}-enabled`,
188
197
  onChange: (event)=>onChange({
189
198
  enabled: event.target.checked
190
199
  }),
@@ -107,7 +107,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
107
107
  }),
108
108
  /*#__PURE__*/ _jsx("p", {
109
109
  className: theme.header.subtitle,
110
- children: list.total === 1 ? '1 endpoint' : `${list.total} endpoints`
110
+ children: list.total === 1 ? labels.endpoints.countOne : labels.endpoints.countMany(list.total)
111
111
  })
112
112
  ]
113
113
  }),
@@ -117,7 +117,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
117
117
  className: theme.button.primary,
118
118
  onClick: handleNew,
119
119
  type: "button",
120
- children: "New endpoint"
120
+ children: labels.endpoints.create
121
121
  })
122
122
  })
123
123
  ]
@@ -153,7 +153,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
153
153
  }) : null,
154
154
  !firstLoad && list.items.length === 0 && !list.error ? /*#__PURE__*/ _jsx("p", {
155
155
  className: theme.list.empty,
156
- children: search.length > 0 ? `No endpoint matches “${search}”. Clear the search to see them all.` : 'No endpoints yet. An endpoint is one call this connection can make — a method, a path, and where the records sit in the answer. Start with New endpoint.'
156
+ children: search.length > 0 ? labels.endpoints.emptySearch(search) : labels.endpoints.empty
157
157
  }) : null,
158
158
  list.items.length > 0 ? /*#__PURE__*/ _jsx("ul", {
159
159
  className: theme.list.wrapper,
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useEffectEvent, useState } from 'react';
4
4
  import { useIntegrationsStore } from '../store';
5
5
  import { useLabels } from '../text/context';
@@ -18,14 +18,24 @@ export function EndpointsTab({ actions }) {
18
18
  const labels = useLabels();
19
19
  const store = useIntegrationsStore();
20
20
  const sourceId = store.selectedSourceId;
21
- const [selected, setSelected] = useState(null);
22
21
  const [creating, setCreating] = useState(false);
23
22
  const [reloadToken, setReloadToken] = useState(0);
24
- // Another connection is another set of endpoints. A form left open across the
23
+ /**
24
+ * The selection is the STORE's, and only the store's.
25
+ *
26
+ * It used to be kept here as well, and the two were reconciled by comparing
27
+ * them on every render. Clicking a second call then left the first form on
28
+ * screen and added the new one under it — two live editors of two different
29
+ * records, both writing to the same hard-coded field ids, growing by one card
30
+ * per click. One source of truth, and one keyed container below, is what makes
31
+ * that impossible rather than unlikely.
32
+ */ const selectedId = store.selectedEndpointId;
33
+ const editing = selectedId ? store.endpoints.find((e)=>e.id === selectedId) ?? null : null;
34
+ // Another connection is another set of calls. A form left open across the
25
35
  // change would be editing a record this connection does not own.
26
36
  const reset = useEffectEvent(()=>{
27
- setSelected(null);
28
37
  setCreating(false);
38
+ store.selectEndpoint(null);
29
39
  });
30
40
  useEffect(()=>{
31
41
  reset();
@@ -33,29 +43,26 @@ export function EndpointsTab({ actions }) {
33
43
  sourceId
34
44
  ]);
35
45
  const handleNew = ()=>{
36
- setSelected(null);
46
+ store.selectEndpoint(null);
37
47
  setCreating(true);
38
48
  };
39
49
  const handleSelect = (endpoint)=>{
40
- setSelected(endpoint);
41
50
  setCreating(false);
51
+ store.selectEndpoint(endpoint.id);
42
52
  };
43
53
  const handleSaved = (saved)=>{
44
- setSelected(saved);
45
54
  setCreating(false);
46
- // Re-selecting drops the old sample: it described the endpoint as it was
55
+ // Re-selecting drops the old sample: it described the call as it was
47
56
  // configured a moment ago, and the mapping would be built from it.
48
57
  store.selectEndpoint(saved.id);
49
58
  setReloadToken((token)=>token + 1);
50
59
  };
51
60
  const handleDeleted = ()=>{
52
- setSelected(null);
53
61
  setCreating(false);
54
62
  store.selectEndpoint(null);
55
63
  setReloadToken((token)=>token + 1);
56
64
  };
57
65
  const handleCancel = ()=>{
58
- setSelected(null);
59
66
  setCreating(false);
60
67
  store.selectEndpoint(null);
61
68
  };
@@ -65,9 +72,6 @@ export function EndpointsTab({ actions }) {
65
72
  children: labels.endpoints.selectConnection
66
73
  });
67
74
  }
68
- // Guarded against the store: the selection is the store's, and a record left
69
- // over from the previous one must not be what the form shows.
70
- const editing = selected && selected.id === store.selectedEndpointId ? selected : null;
71
75
  return /*#__PURE__*/ _jsxs("div", {
72
76
  className: "grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,18rem)_minmax(0,1fr)] xl:gap-5",
73
77
  children: [
@@ -78,26 +82,28 @@ export function EndpointsTab({ actions }) {
78
82
  reloadToken: reloadToken,
79
83
  sourceId: sourceId
80
84
  }, sourceId),
81
- /*#__PURE__*/ _jsxs("div", {
85
+ /*#__PURE__*/ _jsx("div", {
82
86
  className: theme.layout.main,
83
- children: [
84
- editing || creating ? /*#__PURE__*/ _jsx(EndpointForm, {
85
- actions: actions,
86
- endpoint: editing,
87
- onCancel: handleCancel,
88
- onDeleted: handleDeleted,
89
- onSaved: handleSaved,
90
- sourceId: sourceId
91
- }, editing?.id ?? 'new') : /*#__PURE__*/ _jsx("p", {
92
- className: theme.list.empty,
93
- children: "Select an endpoint to edit it, or add one. An endpoint is a single call — a method, a path, and where in the answer the records sit."
94
- }),
95
- editing ? /*#__PURE__*/ _jsx(EndpointSample, {
96
- actions: actions,
97
- endpoint: editing
98
- }, editing.id) : null
99
- ]
100
- })
87
+ children: editing || creating ? /*#__PURE__*/ _jsxs(_Fragment, {
88
+ children: [
89
+ /*#__PURE__*/ _jsx(EndpointForm, {
90
+ actions: actions,
91
+ endpoint: editing,
92
+ onCancel: handleCancel,
93
+ onDeleted: handleDeleted,
94
+ onSaved: handleSaved,
95
+ sourceId: sourceId
96
+ }),
97
+ editing ? /*#__PURE__*/ _jsx(EndpointSample, {
98
+ actions: actions,
99
+ endpoint: editing
100
+ }) : null
101
+ ]
102
+ }) : /*#__PURE__*/ _jsx("p", {
103
+ className: theme.list.empty,
104
+ children: labels.endpoints.pickOne
105
+ })
106
+ }, editing?.id ?? (creating ? 'new' : 'none'))
101
107
  ]
102
108
  });
103
109
  }