From ee5b1c29e04ae348d9a84771388b7ca9a67f0028 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 30 Jun 2026 10:17:18 +0200 Subject: [PATCH] Update TOTP create to hide name input by default in browser extension (#2216) --- .../popup/components/Dialogs/ModalWrapper.tsx | 12 +- .../components/Items/Details/TotpBlock.tsx | 4 +- .../components/Items/Details/TotpEditor.tsx | 160 +++++++++++++----- .../src/i18n/locales/en.json | 1 + 4 files changed, 129 insertions(+), 48 deletions(-) diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx index d4f7091bb..8fc7cb331 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx @@ -65,10 +65,10 @@ const ModalWrapper: React.FC = ({ } /** - * Handle click on the container (outside modal content) to close. + * Handle mousedown on the container (outside modal content) to close. */ - const handleContainerClick = (e: React.MouseEvent): void => { - // Only close if clicking directly on the container, not the modal content + const handleContainerMouseDown = (e: React.MouseEvent): void => { + // Only close if the press started directly on the container, not the modal content if (e.target === e.currentTarget) { onClose(); } @@ -79,13 +79,13 @@ const ModalWrapper: React.FC = ({ {/* Backdrop */}
- {/* Modal container - clicking here (outside modal content) also closes */} + {/* Modal container - pressing here (outside modal content) also closes */}
{/* Header - only show as block if title exists */} diff --git a/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpBlock.tsx b/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpBlock.tsx index 252f9cf15..b72de6271 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpBlock.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpBlock.tsx @@ -177,11 +177,11 @@ const TotpBlock: React.FC = ({ itemId }) => { key={totpCode.Id} className={`w-full text-left p-2 ps-3 pe-3 rounded bg-white dark:bg-gray-800 shadow hover:shadow-md transition-all border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700`} onClick={() => copyToClipboard(currentCodes[totpCode.Id], totpCode.Id)} - aria-label={`Copy ${totpCode.Name} code`} + aria-label={`Copy ${totpCode.Name || t('totp.defaultName')} code`} >
-

{totpCode.Name}

+

{totpCode.Name || t('totp.defaultName')}

diff --git a/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpEditor.tsx b/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpEditor.tsx index 6372999a2..2c9848231 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpEditor.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Items/Details/TotpEditor.tsx @@ -44,9 +44,11 @@ const TotpEditor: React.FC = ({ }) => { const { t } = useTranslation(); const [formError, setFormError] = useState(null); + const [showNameField, setShowNameField] = useState(!!formData.name); const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [editingTotpCode, setEditingTotpCode] = useState(null); const [editName, setEditName] = useState(''); + const [showEditNameField, setShowEditNameField] = useState(false); const [editSecret, setEditSecret] = useState(''); const [showQrCode, setShowQrCode] = useState(false); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(null); @@ -84,7 +86,8 @@ const TotpEditor: React.FC = ({ throw new Error(t('totp.errors.invalidSecretKey')); } - return { secretKey, name: name || t('totp.defaultName') }; + // Name is optional; keep it blank when none was provided or derived. + return { secretKey, name }; }; /** @@ -96,6 +99,7 @@ const TotpEditor: React.FC = ({ formData: { name: '', secretKey: '' } }); setFormError(null); + setShowNameField(false); }; /** @@ -107,6 +111,7 @@ const TotpEditor: React.FC = ({ formData: { name: '', secretKey: '' } }); setFormError(null); + setShowNameField(false); }; /** @@ -119,6 +124,14 @@ const TotpEditor: React.FC = ({ }); }; + /** + * Hides the optional name field again and clears any entered value + */ + const hideNameField = (): void => { + updateFormData({ name: '' }); + setShowNameField(false); + }; + /** * Handles adding a new TOTP code */ @@ -209,7 +222,9 @@ const TotpEditor: React.FC = ({ */ const showEditModal = (totpCode: TotpCode): void => { setEditingTotpCode(totpCode); + // Only reveal the name field when a name was actually set. setEditName(totpCode.Name); + setShowEditNameField(!!totpCode.Name); setEditSecret(totpCode.SecretKey); setIsEditModalOpen(true); setShowQrCode(false); @@ -222,11 +237,21 @@ const TotpEditor: React.FC = ({ setIsEditModalOpen(false); setEditingTotpCode(null); setEditName(''); + setShowEditNameField(false); setEditSecret(''); setShowQrCode(false); setQrCodeDataUrl(null); }; + /** + * Hides the edit name field again and clears any entered value so the code + * falls back to the default name on save. + */ + const hideEditNameField = (): void => { + setEditName(''); + setShowEditNameField(false); + }; + /** * Saves the edited TOTP code */ @@ -235,9 +260,12 @@ const TotpEditor: React.FC = ({ return; } + // The name is optional; store it as-is (blank when the user left it empty). + const finalName = editName.trim(); + const updatedTotpCodes = totpCodes.map(tc => tc.Id === editingTotpCode.Id - ? { ...tc, Name: editName, SecretKey: editSecret } + ? { ...tc, Name: finalName, SecretKey: editSecret } : tc ); @@ -314,7 +342,7 @@ const TotpEditor: React.FC = ({ {isAddFormVisible && (
-
+

{t('totp.addCode')}

@@ -342,19 +370,6 @@ const TotpEditor: React.FC = ({
)} -
- - updateFormData({ name: e.target.value })} - className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" - /> -
-
-
- -
+ {/* Name is optional and hidden by default */} + {showNameField ? ( +
+
+ + +
+ updateFormData({ name: e.target.value })} + className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" + /> +
+ ) : ( +
+ +
+ )} + +
)} @@ -396,7 +452,7 @@ const TotpEditor: React.FC = ({

- {totpCode.Name} + {totpCode.Name || t('totp.defaultName')}

@@ -442,19 +498,6 @@ const TotpEditor: React.FC = ({
{editingTotpCode && (
-
- - setEditName(e.target.value)} - className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:text-white" - placeholder={t('totp.nameOptional')} - /> -
-
+ {/* Name is optional; hidden by default unless a custom name was set. */} + {showEditNameField ? ( +
+
+ + +
+ setEditName(e.target.value)} + className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:text-white" + /> +
+ ) : ( + + )} +
diff --git a/apps/browser-extension/src/i18n/locales/en.json b/apps/browser-extension/src/i18n/locales/en.json index 33f39e385..27c041a71 100644 --- a/apps/browser-extension/src/i18n/locales/en.json +++ b/apps/browser-extension/src/i18n/locales/en.json @@ -329,6 +329,7 @@ "addCode": "Add 2FA Code", "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.", "nameOptional": "Name (optional)", + "addName": "Add a name (optional)", "secretKey": "Secret Key", "saveToViewCode": "Save to view code", "defaultName": "Authenticator",