rn-backstage 1.4.7 → 1.5.1

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.
Files changed (52) hide show
  1. package/README.md +44 -0
  2. package/lib/commonjs/Backstage.js +4 -1
  3. package/lib/commonjs/Backstage.js.map +1 -1
  4. package/lib/commonjs/bug-report.js.map +1 -1
  5. package/lib/commonjs/components/BackstagePanel.js +14 -2
  6. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  7. package/lib/commonjs/components/BugReportComposer.js.map +1 -1
  8. package/lib/commonjs/components/EnvironmentTab.js +772 -0
  9. package/lib/commonjs/components/EnvironmentTab.js.map +1 -0
  10. package/lib/commonjs/components/FlagsTab.js.map +1 -1
  11. package/lib/commonjs/components/FloatingPill.js.map +1 -1
  12. package/lib/commonjs/components/StorageTab.js.map +1 -1
  13. package/lib/commonjs/constants.js +17 -0
  14. package/lib/commonjs/constants.js.map +1 -1
  15. package/lib/commonjs/index.js +7 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/types.js +1 -0
  18. package/lib/commonjs/types.js.map +1 -1
  19. package/lib/module/Backstage.js +4 -1
  20. package/lib/module/Backstage.js.map +1 -1
  21. package/lib/module/bug-report.js.map +1 -1
  22. package/lib/module/components/BackstagePanel.js +14 -2
  23. package/lib/module/components/BackstagePanel.js.map +1 -1
  24. package/lib/module/components/BugReportComposer.js.map +1 -1
  25. package/lib/module/components/EnvironmentTab.js +767 -0
  26. package/lib/module/components/EnvironmentTab.js.map +1 -0
  27. package/lib/module/components/FlagsTab.js.map +1 -1
  28. package/lib/module/components/FloatingPill.js.map +1 -1
  29. package/lib/module/components/StorageTab.js.map +1 -1
  30. package/lib/module/constants.js +17 -0
  31. package/lib/module/constants.js.map +1 -1
  32. package/lib/module/index.js +1 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/types.js +2 -0
  35. package/lib/module/types.js.map +1 -1
  36. package/lib/typescript/Backstage.d.ts.map +1 -1
  37. package/lib/typescript/bug-report.d.ts.map +1 -1
  38. package/lib/typescript/components/BackstagePanel.d.ts +2 -1
  39. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  40. package/lib/typescript/components/BugReportComposer.d.ts.map +1 -1
  41. package/lib/typescript/components/EnvironmentTab.d.ts +8 -0
  42. package/lib/typescript/components/EnvironmentTab.d.ts.map +1 -0
  43. package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
  44. package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
  45. package/lib/typescript/components/StorageTab.d.ts.map +1 -1
  46. package/lib/typescript/constants.d.ts +16 -0
  47. package/lib/typescript/constants.d.ts.map +1 -1
  48. package/lib/typescript/index.d.ts +2 -1
  49. package/lib/typescript/index.d.ts.map +1 -1
  50. package/lib/typescript/types.d.ts +50 -0
  51. package/lib/typescript/types.d.ts.map +1 -1
  52. package/package.json +2 -2
@@ -0,0 +1,772 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EnvironmentTab = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _constants = require("../constants");
10
+ var _ThemeContext = require("../ThemeContext");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
13
+ // ─── Constants ───────────────────────────────────────────────────────────────
14
+
15
+ const STORAGE_KEY_PREFIX = 'backstage.env.credentials.';
16
+
17
+ // ─── Credential Editor Modal ─────────────────────────────────────────────────
18
+
19
+ const EditorModal = ({
20
+ visible,
21
+ title,
22
+ credentialName,
23
+ fieldValues,
24
+ credentialFields = [],
25
+ onChangeName,
26
+ onChangeField,
27
+ onSave,
28
+ onCancel,
29
+ theme
30
+ }) => {
31
+ const s = (0, _react.useMemo)(() => createStyles(theme), [theme]);
32
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
33
+ testID: _constants.TestIDs.environmentTab.editorModal,
34
+ visible: visible,
35
+ animationType: "slide",
36
+ transparent: false,
37
+ onRequestClose: onCancel,
38
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.SafeAreaView, {
39
+ style: s.modalContainer,
40
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
41
+ style: s.modalHeader,
42
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
43
+ testID: _constants.TestIDs.environmentTab.editorCancelButton,
44
+ onPress: onCancel,
45
+ hitSlop: {
46
+ top: 8,
47
+ bottom: 8,
48
+ left: 8,
49
+ right: 8
50
+ },
51
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
52
+ style: s.modalCancelText,
53
+ children: "Cancel"
54
+ })
55
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
56
+ style: s.modalTitle,
57
+ children: title
58
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
59
+ testID: _constants.TestIDs.environmentTab.editorSaveButton,
60
+ onPress: onSave,
61
+ hitSlop: {
62
+ top: 8,
63
+ bottom: 8,
64
+ left: 8,
65
+ right: 8
66
+ },
67
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
68
+ style: s.modalSaveText,
69
+ children: "Save"
70
+ })
71
+ })]
72
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.KeyboardAvoidingView, {
73
+ style: s.modalBody,
74
+ behavior: _reactNative.Platform.OS === 'ios' ? 'padding' : undefined,
75
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
76
+ contentContainerStyle: s.modalBodyContent,
77
+ keyboardShouldPersistTaps: "handled",
78
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
79
+ style: s.modalLabel,
80
+ children: "Name"
81
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
82
+ testID: _constants.TestIDs.environmentTab.editorNameInput,
83
+ style: s.modalInput,
84
+ value: credentialName,
85
+ onChangeText: onChangeName,
86
+ placeholder: "e.g., QA Account, Admin",
87
+ placeholderTextColor: theme.textMuted,
88
+ autoCapitalize: "words",
89
+ autoFocus: true
90
+ }), credentialFields.map(field => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
91
+ style: {
92
+ marginTop: 16
93
+ },
94
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
95
+ style: s.modalLabel,
96
+ children: field.label
97
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
98
+ testID: _constants.TestIDs.environmentTab.editorFieldInput(field.key),
99
+ style: [s.modalInput, field.secureTextEntry ? undefined : undefined],
100
+ value: fieldValues[field.key] || '',
101
+ onChangeText: val => onChangeField(field.key, val),
102
+ placeholder: field.placeholder || field.label,
103
+ placeholderTextColor: theme.textMuted,
104
+ secureTextEntry: field.secureTextEntry,
105
+ autoCapitalize: field.autoCapitalize ?? 'none',
106
+ autoCorrect: false,
107
+ keyboardType: field.keyboardType
108
+ })]
109
+ }, field.key))]
110
+ })
111
+ })]
112
+ })
113
+ });
114
+ };
115
+
116
+ // ─── Credential Row ──────────────────────────────────────────────────────────
117
+
118
+ const CredentialRow = ({
119
+ envKey,
120
+ index,
121
+ credential,
122
+ envColor,
123
+ onLogin,
124
+ onEdit,
125
+ onDelete,
126
+ loggingIn,
127
+ theme
128
+ }) => {
129
+ const s = (0, _react.useMemo)(() => createStyles(theme), [theme]);
130
+ const fieldCount = Object.keys(credential.values).length;
131
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
132
+ testID: _constants.TestIDs.environmentTab.credentialRow(envKey, index),
133
+ style: s.credRow,
134
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
135
+ style: s.credRowLeft,
136
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
137
+ style: s.credName,
138
+ numberOfLines: 1,
139
+ children: credential.name
140
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
141
+ style: s.credMeta,
142
+ children: [fieldCount, " field", fieldCount !== 1 ? 's' : '']
143
+ })]
144
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
145
+ style: s.credRowActions,
146
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
147
+ testID: _constants.TestIDs.environmentTab.credentialLogin(envKey, index),
148
+ style: [s.credActionButton, {
149
+ backgroundColor: envColor
150
+ }],
151
+ onPress: onLogin,
152
+ disabled: loggingIn,
153
+ activeOpacity: 0.7,
154
+ hitSlop: {
155
+ top: 4,
156
+ bottom: 4,
157
+ left: 4,
158
+ right: 4
159
+ },
160
+ children: loggingIn ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
161
+ size: "small",
162
+ color: "#FFF"
163
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
164
+ style: s.credActionLoginText,
165
+ children: "Login"
166
+ })
167
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
168
+ testID: _constants.TestIDs.environmentTab.credentialEdit(envKey, index),
169
+ onPress: onEdit,
170
+ hitSlop: {
171
+ top: 8,
172
+ bottom: 8,
173
+ left: 8,
174
+ right: 8
175
+ },
176
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
177
+ style: s.credActionIcon,
178
+ children: "\u270E"
179
+ })
180
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
181
+ testID: _constants.TestIDs.environmentTab.credentialDelete(envKey, index),
182
+ onPress: onDelete,
183
+ hitSlop: {
184
+ top: 8,
185
+ bottom: 8,
186
+ left: 8,
187
+ right: 8
188
+ },
189
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
190
+ style: [s.credActionIcon, s.credDeleteIcon],
191
+ children: "\u2715"
192
+ })
193
+ })]
194
+ })]
195
+ });
196
+ };
197
+
198
+ // ─── Main Component ──────────────────────────────────────────────────────────
199
+
200
+ const EnvironmentTab = ({
201
+ config
202
+ }) => {
203
+ const {
204
+ environments,
205
+ activeEnvironment,
206
+ onEnvironmentChange,
207
+ credentialFields = [],
208
+ onLogin,
209
+ initialCredentials,
210
+ storageAdapter
211
+ } = config;
212
+ const theme = (0, _ThemeContext.useBackstageTheme)();
213
+ const s = (0, _react.useMemo)(() => createStyles(theme), [theme]);
214
+
215
+ // ── Credentials state: Record<envKey, SavedCredential[]> ──
216
+ const [credentials, setCredentials] = (0, _react.useState)(() => {
217
+ const init = {};
218
+ for (const env of environments) {
219
+ init[env.key] = initialCredentials?.[env.key] ? [...initialCredentials[env.key]] : [];
220
+ }
221
+ return init;
222
+ });
223
+ const [loadingCredentials, setLoadingCredentials] = (0, _react.useState)(!!storageAdapter);
224
+ const [loggingInKey, setLoggingInKey] = (0, _react.useState)(null);
225
+
226
+ // ── Editor modal state ─────────────────────────────────────
227
+ const [editorVisible, setEditorVisible] = (0, _react.useState)(false);
228
+ const [editorEnvKey, setEditorEnvKey] = (0, _react.useState)('');
229
+ const [editorIndex, setEditorIndex] = (0, _react.useState)(null); // null = add new
230
+ const [editorName, setEditorName] = (0, _react.useState)('');
231
+ const [editorFields, setEditorFields] = (0, _react.useState)({});
232
+
233
+ // ── Load persisted credentials on mount ────────────────────
234
+ // Merges stored credentials with initialCredentials.
235
+ // initialCredentials always take priority — deduplication uses values.email.
236
+ (0, _react.useEffect)(() => {
237
+ if (!storageAdapter) return;
238
+ let cancelled = false;
239
+ const load = async () => {
240
+ try {
241
+ for (const env of environments) {
242
+ const stored = await storageAdapter.getItem(STORAGE_KEY_PREFIX + env.key);
243
+ if (stored && !cancelled) {
244
+ try {
245
+ const parsed = JSON.parse(stored);
246
+ if (Array.isArray(parsed)) {
247
+ // Merge: initialCredentials take priority, matched by values.email
248
+ const initial = initialCredentials?.[env.key] || [];
249
+ const initialEmails = new Set(initial.map(c => c.values?.email).filter(Boolean));
250
+ // Keep stored credentials that don't conflict with initial ones
251
+ const storedOnly = parsed.filter(c => !c.values?.email || !initialEmails.has(c.values.email));
252
+ // For initial credentials, prefer stored version's extra fields but keep initial's core values
253
+ const merged = initial.map(ic => {
254
+ const storedMatch = parsed.find(sc => sc.values?.email && sc.values.email === ic.values?.email);
255
+ if (storedMatch) {
256
+ return {
257
+ ...storedMatch,
258
+ name: ic.name,
259
+ values: {
260
+ ...storedMatch.values,
261
+ ...ic.values
262
+ }
263
+ };
264
+ }
265
+ return ic;
266
+ });
267
+ const finalCreds = [...merged, ...storedOnly];
268
+ setCredentials(prev => ({
269
+ ...prev,
270
+ [env.key]: finalCreds
271
+ }));
272
+
273
+ // Persist the merged result so storage stays in sync
274
+ storageAdapter.setItem(STORAGE_KEY_PREFIX + env.key, JSON.stringify(finalCreds)).catch(() => {});
275
+ }
276
+ } catch {
277
+ // Invalid JSON — ignore
278
+ }
279
+ }
280
+ }
281
+ } finally {
282
+ if (!cancelled) setLoadingCredentials(false);
283
+ }
284
+ };
285
+ load();
286
+ return () => {
287
+ cancelled = true;
288
+ };
289
+ }, [storageAdapter, environments, initialCredentials]);
290
+
291
+ // ── Persist credentials to storage ─────────────────────────
292
+ const persistCredentials = (0, _react.useCallback)((envKey, creds) => {
293
+ if (!storageAdapter) return;
294
+ storageAdapter.setItem(STORAGE_KEY_PREFIX + envKey, JSON.stringify(creds)).catch(() => {});
295
+ }, [storageAdapter]);
296
+
297
+ // ── Editor open/close ──────────────────────────────────────
298
+ const openAddEditor = (0, _react.useCallback)(envKey => {
299
+ setEditorEnvKey(envKey);
300
+ setEditorIndex(null);
301
+ setEditorName('');
302
+ setEditorFields({});
303
+ setEditorVisible(true);
304
+ }, []);
305
+ const openEditEditor = (0, _react.useCallback)((envKey, index) => {
306
+ const cred = credentials[envKey]?.[index];
307
+ if (!cred) return;
308
+ setEditorEnvKey(envKey);
309
+ setEditorIndex(index);
310
+ setEditorName(cred.name);
311
+ setEditorFields({
312
+ ...cred.values
313
+ });
314
+ setEditorVisible(true);
315
+ }, [credentials]);
316
+ const closeEditor = (0, _react.useCallback)(() => {
317
+ setEditorVisible(false);
318
+ setEditorEnvKey('');
319
+ setEditorIndex(null);
320
+ setEditorName('');
321
+ setEditorFields({});
322
+ }, []);
323
+ const saveEditor = (0, _react.useCallback)(() => {
324
+ const trimmedName = editorName.trim();
325
+ if (!trimmedName) {
326
+ _reactNative.Alert.alert('Error', 'Credential name cannot be empty');
327
+ return;
328
+ }
329
+ const newCred = {
330
+ name: trimmedName,
331
+ values: {
332
+ ...editorFields
333
+ }
334
+ };
335
+ setCredentials(prev => {
336
+ const envCreds = [...(prev[editorEnvKey] || [])];
337
+ if (editorIndex !== null) {
338
+ // Edit existing
339
+ envCreds[editorIndex] = newCred;
340
+ } else {
341
+ // Add new
342
+ envCreds.push(newCred);
343
+ }
344
+ persistCredentials(editorEnvKey, envCreds);
345
+ return {
346
+ ...prev,
347
+ [editorEnvKey]: envCreds
348
+ };
349
+ });
350
+ closeEditor();
351
+ }, [editorName, editorFields, editorEnvKey, editorIndex, persistCredentials, closeEditor]);
352
+ const updateEditorField = (0, _react.useCallback)((fieldKey, value) => {
353
+ setEditorFields(prev => ({
354
+ ...prev,
355
+ [fieldKey]: value
356
+ }));
357
+ }, []);
358
+
359
+ // ── Delete credential ──────────────────────────────────────
360
+ const confirmDelete = (0, _react.useCallback)((envKey, index) => {
361
+ const cred = credentials[envKey]?.[index];
362
+ if (!cred) return;
363
+ _reactNative.Alert.alert('Delete Credential', `Remove "${cred.name}"?`, [{
364
+ text: 'Cancel',
365
+ style: 'cancel'
366
+ }, {
367
+ text: 'Delete',
368
+ style: 'destructive',
369
+ onPress: () => {
370
+ setCredentials(prev => {
371
+ const envCreds = [...(prev[envKey] || [])];
372
+ envCreds.splice(index, 1);
373
+ persistCredentials(envKey, envCreds);
374
+ return {
375
+ ...prev,
376
+ [envKey]: envCreds
377
+ };
378
+ });
379
+ }
380
+ }]);
381
+ }, [credentials, persistCredentials]);
382
+
383
+ // ── Handle login ───────────────────────────────────────────
384
+ const handleLogin = (0, _react.useCallback)(async (envKey, index) => {
385
+ if (!onLogin) return;
386
+ const cred = credentials[envKey]?.[index];
387
+ if (!cred) return;
388
+ const loginKey = `${envKey}:${index}`;
389
+ setLoggingInKey(loginKey);
390
+ try {
391
+ await onLogin(envKey, cred.values);
392
+ } finally {
393
+ setLoggingInKey(null);
394
+ }
395
+ }, [onLogin, credentials]);
396
+ const activeEnv = environments.find(e => e.key === activeEnvironment);
397
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
398
+ style: s.container,
399
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
400
+ testID: _constants.TestIDs.environmentTab.container,
401
+ data: environments,
402
+ keyExtractor: env => env.key,
403
+ contentContainerStyle: s.contentContainer,
404
+ showsVerticalScrollIndicator: false,
405
+ keyboardShouldPersistTaps: "handled",
406
+ renderItem: ({
407
+ item: env
408
+ }) => {
409
+ const isActive = env.key === activeEnvironment;
410
+ const envColor = env.color || theme.accent;
411
+ const envCreds = credentials[env.key] || [];
412
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
413
+ testID: _constants.TestIDs.environmentTab.environmentCard(env.key),
414
+ style: [s.card, isActive && s.cardActive, isActive && {
415
+ borderColor: envColor
416
+ }],
417
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
418
+ testID: _constants.TestIDs.environmentTab.radioButton(env.key),
419
+ style: s.cardHeader,
420
+ onPress: () => onEnvironmentChange(env.key),
421
+ activeOpacity: 0.7,
422
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
423
+ style: s.radioRow,
424
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
425
+ style: [s.radio, isActive && s.radioActive, isActive && {
426
+ borderColor: envColor
427
+ }],
428
+ children: isActive && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
429
+ style: [s.radioDot, {
430
+ backgroundColor: envColor
431
+ }]
432
+ })
433
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
434
+ style: s.envInfo,
435
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
436
+ style: [s.envLabel, isActive && {
437
+ color: envColor
438
+ }],
439
+ children: env.label
440
+ }), env.baseUrl && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
441
+ style: s.envUrl,
442
+ numberOfLines: 1,
443
+ children: env.baseUrl
444
+ })]
445
+ })]
446
+ }), isActive && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
447
+ style: [s.activeBadge, {
448
+ backgroundColor: envColor + '20'
449
+ }],
450
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
451
+ style: [s.activeBadgeText, {
452
+ color: envColor
453
+ }],
454
+ children: "Active"
455
+ })
456
+ })]
457
+ }), isActive && credentialFields.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
458
+ style: s.credentialsContainer,
459
+ children: loadingCredentials ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
460
+ style: s.loadingContainer,
461
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
462
+ size: "small",
463
+ color: theme.textMuted
464
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
465
+ style: s.loadingText,
466
+ children: "Loading credentials\u2026"
467
+ })]
468
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
469
+ children: [envCreds.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
470
+ style: s.emptyState,
471
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
472
+ style: s.emptyText,
473
+ children: "No saved credentials"
474
+ })
475
+ }) : envCreds.map((cred, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(CredentialRow, {
476
+ envKey: env.key,
477
+ index: index,
478
+ credential: cred,
479
+ envColor: envColor,
480
+ onLogin: () => handleLogin(env.key, index),
481
+ onEdit: () => openEditEditor(env.key, index),
482
+ onDelete: () => confirmDelete(env.key, index),
483
+ loggingIn: loggingInKey === `${env.key}:${index}`,
484
+ theme: theme
485
+ }, `${cred.name}-${index}`)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
486
+ testID: _constants.TestIDs.environmentTab.addButton(env.key),
487
+ style: [s.addButton, {
488
+ borderColor: envColor
489
+ }],
490
+ onPress: () => openAddEditor(env.key),
491
+ activeOpacity: 0.7,
492
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
493
+ style: [s.addButtonText, {
494
+ color: envColor
495
+ }],
496
+ children: "\uFF0B Add Credential"
497
+ })
498
+ })]
499
+ })
500
+ })]
501
+ });
502
+ },
503
+ ListFooterComponent: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
504
+ testID: _constants.TestIDs.environmentTab.activeLabel,
505
+ style: s.footer,
506
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
507
+ style: s.footerText,
508
+ children: ["Active: ", activeEnv?.label ?? activeEnvironment, storageAdapter ? ' · Credentials saved' : '']
509
+ })
510
+ })
511
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(EditorModal, {
512
+ visible: editorVisible,
513
+ title: editorIndex !== null ? 'Edit Credential' : 'Add Credential',
514
+ credentialName: editorName,
515
+ fieldValues: editorFields,
516
+ credentialFields: credentialFields,
517
+ onChangeName: setEditorName,
518
+ onChangeField: updateEditorField,
519
+ onSave: saveEditor,
520
+ onCancel: closeEditor,
521
+ theme: theme
522
+ })]
523
+ });
524
+ };
525
+
526
+ // ─── Styles ──────────────────────────────────────────────────────────────────
527
+ exports.EnvironmentTab = EnvironmentTab;
528
+ const createStyles = t => _reactNative.StyleSheet.create({
529
+ container: {
530
+ flex: 1
531
+ },
532
+ contentContainer: {
533
+ padding: 14,
534
+ paddingBottom: 40
535
+ },
536
+ // ── Card ────────────────────────
537
+ card: {
538
+ backgroundColor: t.surface,
539
+ borderRadius: 12,
540
+ borderWidth: 1.5,
541
+ borderColor: t.border,
542
+ marginBottom: 12,
543
+ overflow: 'hidden'
544
+ },
545
+ cardActive: {
546
+ backgroundColor: t.surfaceElevated
547
+ },
548
+ cardHeader: {
549
+ flexDirection: 'row',
550
+ alignItems: 'center',
551
+ justifyContent: 'space-between',
552
+ padding: 14
553
+ },
554
+ radioRow: {
555
+ flexDirection: 'row',
556
+ alignItems: 'center',
557
+ flex: 1
558
+ },
559
+ radio: {
560
+ width: 20,
561
+ height: 20,
562
+ borderRadius: 10,
563
+ borderWidth: 2,
564
+ borderColor: t.border,
565
+ alignItems: 'center',
566
+ justifyContent: 'center',
567
+ marginRight: 12
568
+ },
569
+ radioActive: {
570
+ borderWidth: 2
571
+ },
572
+ radioDot: {
573
+ width: 10,
574
+ height: 10,
575
+ borderRadius: 5
576
+ },
577
+ envInfo: {
578
+ flex: 1
579
+ },
580
+ envLabel: {
581
+ fontFamily: _constants.MonospaceFont,
582
+ fontSize: 15,
583
+ fontWeight: '700',
584
+ color: t.text
585
+ },
586
+ envUrl: {
587
+ fontFamily: _constants.MonospaceFont,
588
+ fontSize: 11,
589
+ color: t.textMuted,
590
+ marginTop: 3
591
+ },
592
+ activeBadge: {
593
+ borderRadius: 6,
594
+ paddingHorizontal: 8,
595
+ paddingVertical: 3,
596
+ marginLeft: 8
597
+ },
598
+ activeBadgeText: {
599
+ fontFamily: _constants.MonospaceFont,
600
+ fontSize: 10,
601
+ fontWeight: '700',
602
+ textTransform: 'uppercase',
603
+ letterSpacing: 0.5
604
+ },
605
+ // ── Credentials Section ─────────
606
+ credentialsContainer: {
607
+ paddingHorizontal: 14,
608
+ paddingBottom: 14,
609
+ borderTopWidth: _reactNative.StyleSheet.hairlineWidth,
610
+ borderTopColor: t.border,
611
+ paddingTop: 10
612
+ },
613
+ emptyState: {
614
+ alignItems: 'center',
615
+ paddingVertical: 14
616
+ },
617
+ emptyText: {
618
+ fontFamily: _constants.MonospaceFont,
619
+ fontSize: 12,
620
+ color: t.textMuted,
621
+ fontStyle: 'italic'
622
+ },
623
+ // ── Credential Row ──────────────
624
+ credRow: {
625
+ flexDirection: 'row',
626
+ alignItems: 'center',
627
+ justifyContent: 'space-between',
628
+ paddingVertical: 10,
629
+ borderBottomWidth: _reactNative.StyleSheet.hairlineWidth,
630
+ borderBottomColor: t.border
631
+ },
632
+ credRowLeft: {
633
+ flex: 1,
634
+ marginRight: 8
635
+ },
636
+ credName: {
637
+ fontFamily: _constants.MonospaceFont,
638
+ fontSize: 13,
639
+ fontWeight: '700',
640
+ color: t.text
641
+ },
642
+ credMeta: {
643
+ fontFamily: _constants.MonospaceFont,
644
+ fontSize: 10,
645
+ color: t.textMuted,
646
+ marginTop: 2
647
+ },
648
+ credRowActions: {
649
+ flexDirection: 'row',
650
+ alignItems: 'center',
651
+ gap: 10
652
+ },
653
+ credActionButton: {
654
+ borderRadius: 6,
655
+ paddingHorizontal: 12,
656
+ paddingVertical: 5,
657
+ minWidth: 55,
658
+ alignItems: 'center'
659
+ },
660
+ credActionLoginText: {
661
+ fontFamily: _constants.MonospaceFont,
662
+ fontSize: 11,
663
+ fontWeight: '700',
664
+ color: '#FFFFFF'
665
+ },
666
+ credActionIcon: {
667
+ fontSize: 16,
668
+ color: t.textMuted
669
+ },
670
+ credDeleteIcon: {
671
+ color: t.error
672
+ },
673
+ // ── Add Button ──────────────────
674
+ addButton: {
675
+ borderRadius: 8,
676
+ borderWidth: 1,
677
+ borderStyle: 'dashed',
678
+ paddingVertical: 10,
679
+ alignItems: 'center',
680
+ marginTop: 10
681
+ },
682
+ addButtonText: {
683
+ fontFamily: _constants.MonospaceFont,
684
+ fontSize: 12,
685
+ fontWeight: '700'
686
+ },
687
+ // ── Footer ──────────────────────
688
+ footer: {
689
+ alignItems: 'center',
690
+ paddingVertical: 12
691
+ },
692
+ footerText: {
693
+ fontFamily: _constants.MonospaceFont,
694
+ fontSize: 11,
695
+ color: t.textMuted,
696
+ fontStyle: 'italic'
697
+ },
698
+ // ── Loading ─────────────────────
699
+ loadingContainer: {
700
+ flexDirection: 'row',
701
+ alignItems: 'center',
702
+ justifyContent: 'center',
703
+ gap: 8,
704
+ paddingVertical: 12
705
+ },
706
+ loadingText: {
707
+ fontFamily: _constants.MonospaceFont,
708
+ fontSize: 12,
709
+ color: t.textMuted
710
+ },
711
+ // ── Editor Modal ────────────────
712
+ modalContainer: {
713
+ flex: 1,
714
+ backgroundColor: t.background
715
+ },
716
+ modalHeader: {
717
+ flexDirection: 'row',
718
+ alignItems: 'center',
719
+ justifyContent: 'space-between',
720
+ paddingHorizontal: 16,
721
+ paddingVertical: 12,
722
+ borderBottomWidth: 1,
723
+ borderBottomColor: t.border
724
+ },
725
+ modalTitle: {
726
+ fontFamily: _constants.MonospaceFont,
727
+ fontSize: 16,
728
+ fontWeight: '700',
729
+ color: t.text,
730
+ flex: 1,
731
+ textAlign: 'center'
732
+ },
733
+ modalCancelText: {
734
+ fontFamily: _constants.MonospaceFont,
735
+ fontSize: 14,
736
+ color: t.textSecondary,
737
+ fontWeight: '600'
738
+ },
739
+ modalSaveText: {
740
+ fontFamily: _constants.MonospaceFont,
741
+ fontSize: 14,
742
+ color: t.accent,
743
+ fontWeight: '700'
744
+ },
745
+ modalBody: {
746
+ flex: 1
747
+ },
748
+ modalBodyContent: {
749
+ padding: 16
750
+ },
751
+ modalLabel: {
752
+ fontFamily: _constants.MonospaceFont,
753
+ fontSize: 12,
754
+ fontWeight: '700',
755
+ color: t.textMuted,
756
+ textTransform: 'uppercase',
757
+ letterSpacing: 0.5,
758
+ marginBottom: 6
759
+ },
760
+ modalInput: {
761
+ fontFamily: _constants.MonospaceFont,
762
+ fontSize: 13,
763
+ color: t.text,
764
+ backgroundColor: t.surfaceElevated,
765
+ borderRadius: 8,
766
+ borderWidth: 1,
767
+ borderColor: t.border,
768
+ paddingHorizontal: 12,
769
+ paddingVertical: 10
770
+ }
771
+ });
772
+ //# sourceMappingURL=EnvironmentTab.js.map