From 3ef3c684b38ecba1e4b76564f39b7fbc28e9c187 Mon Sep 17 00:00:00 2001 From: gwdgithubnom Date: Wed, 27 May 2026 22:46:00 +0800 Subject: [PATCH] fix(ui): add show/hide toggle for API key inputs and populate key on edit Add eye toggle button for all API key input fields so users can verify their keys before saving. Changes: - BaseProviderForm: wrap single API key field (Gemini/Codex/Claude/Vertex) with passwordField + toggle button - BaseProviderForm: wrap per-entry API key fields (OpenAI-compatible) with passwordField + toggle button - BaseProviderForm: extract applyRawApiKey helper to populate apiKey from resource.raw in edit mode - BaseProviderForm: sync initialFormSignature with applyRawApiKey to prevent false isDirty - sharedForm.module.scss: add .passwordField, .passwordInput, .passwordToggle styles - i18n: add showApiKey/hideApiKey keys to en, zh-CN, zh-TW, ru locale files - accessibility: add aria-label and title attributes on both toggle buttons --- .../sheets/forms/BaseProviderForm.tsx | 153 ++++++++++++++---- .../sheets/forms/sharedForm.module.scss | 58 +++++++ src/i18n/locales/en.json | 2 + src/i18n/locales/ru.json | 2 + src/i18n/locales/zh-CN.json | 2 + src/i18n/locales/zh-TW.json | 2 + 6 files changed, 186 insertions(+), 33 deletions(-) diff --git a/src/features/providers/sheets/forms/BaseProviderForm.tsx b/src/features/providers/sheets/forms/BaseProviderForm.tsx index 03dd197..24fa4ea 100644 --- a/src/features/providers/sheets/forms/BaseProviderForm.tsx +++ b/src/features/providers/sheets/forms/BaseProviderForm.tsx @@ -4,6 +4,8 @@ import { IconAlertTriangle, IconCheckCircle2, IconDownload, + IconEye, + IconEyeOff, IconLoader2, IconPlus, IconX, @@ -65,6 +67,19 @@ const headersObjectToText = (headers?: Record): string => const stripDisableAllRule = (list?: string[]): string[] => (list ?? []).filter((s) => s.trim() !== '*'); +/** Populate apiKey from resource.raw when editing a non-OpenAI provider. */ +const applyRawApiKey = ( + brand: Exclude, + resource: ProviderResource | null, + mode: 'create' | 'edit', + form: ProviderEntryFormInput +): void => { + if (mode === 'edit' && resource && brand !== 'openaiCompatibility') { + const rawKey = (resource.raw as { apiKey?: string } | undefined)?.apiKey ?? ''; + if (rawKey) form.apiKey = rawKey; + } +}; + function buildInitialForm( brand: Exclude, resource: ProviderResource | null, @@ -204,13 +219,37 @@ export function BaseProviderForm({ const { t } = useTranslation(); const descriptor = PROVIDER_DESCRIPTORS[brand]; const fid = useId(); - const [form, setForm] = useState(() => - buildInitialForm(brand, resource, mode) - ); - const [initialFormSignature] = useState(() => - JSON.stringify(buildInitialForm(brand, resource, mode)) - ); + const [form, setForm] = useState(() => { + const initial = buildInitialForm(brand, resource, mode); + applyRawApiKey(brand, resource, mode, initial); + return initial; + }); + const [initialFormSignature] = useState(() => { + const initial = buildInitialForm(brand, resource, mode); + applyRawApiKey(brand, resource, mode, initial); + return JSON.stringify(initial); + }); const [error, setError] = useState(null); + const [showPasswords, setShowPasswords] = useState>(new Set()); + const [showSingleApiKey, setShowSingleApiKey] = useState(false); + + // Reset password visibility when the sheet closes or resource changes + useEffect(() => { + setShowPasswords(new Set()); + setShowSingleApiKey(false); + }, [resource?.id, mode]); + + const togglePasswordVisibility = (idx: number) => { + setShowPasswords((prev) => { + const next = new Set(prev); + if (next.has(idx)) { + next.delete(idx); + } else { + next.add(idx); + } + return next; + }); + }; const isDirty = useMemo( () => JSON.stringify(form) !== initialFormSignature, @@ -454,19 +493,43 @@ export function BaseProviderForm({ - updateField('apiKey', e.target.value)} - placeholder={ - mode === 'edit' - ? t('providersPage.form.apiKeyEditPlaceholder') - : t('providersPage.form.apiKeyCreatePlaceholder') - } - disabled={mutating} - /> +
+ updateField('apiKey', e.target.value)} + placeholder={ + mode === 'edit' + ? t('providersPage.form.apiKeyEditPlaceholder') + : t('providersPage.form.apiKeyCreatePlaceholder') + } + disabled={mutating} + /> + +
) : null} @@ -695,21 +758,45 @@ export function BaseProviderForm({ - - updateField( - 'apiKeyEntries', - apiKeyEntries.map((it, i) => - i === idx ? { ...it, apiKey: e.target.value } : it +
+ + updateField( + 'apiKeyEntries', + apiKeyEntries.map((it, i) => + i === idx ? { ...it, apiKey: e.target.value } : it + ) ) - ) - } - disabled={mutating} - placeholder={t('providersPage.form.apiKeyCreatePlaceholder')} - /> + } + disabled={mutating} + placeholder={t('providersPage.form.apiKeyCreatePlaceholder')} + /> + +