From 08d5bfc40cfa9bb3e4a97de790fd960ac36c28cb Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 30 Jun 2026 21:15:45 +0200 Subject: [PATCH] Update TOTP create to hide name input by default in mobile app (#2216) --- .../components/items/details/TotpEditor.tsx | 136 ++++++++++++++---- .../components/items/details/TotpSection.tsx | 2 +- apps/mobile-app/i18n/locales/en.json | 5 +- 3 files changed, 112 insertions(+), 31 deletions(-) diff --git a/apps/mobile-app/components/items/details/TotpEditor.tsx b/apps/mobile-app/components/items/details/TotpEditor.tsx index 2eedb3b3e..62f64891c 100644 --- a/apps/mobile-app/components/items/details/TotpEditor.tsx +++ b/apps/mobile-app/components/items/details/TotpEditor.tsx @@ -50,9 +50,11 @@ export const TotpEditor: React.FC = ({ const [isAddFormVisible, setIsAddFormVisible] = useState(false); const [formData, setFormData] = useState({ name: '', secretKey: '' }); const [formError, setFormError] = useState(null); + const [showNameField, setShowNameField] = useState(false); 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 hasLaunchedScanner = React.useRef(false); @@ -78,6 +80,7 @@ export const TotpEditor: React.FC = ({ hideAddChoiceModal(); setFormData({ name: '', secretKey: '' }); setFormError(null); + setShowNameField(false); setIsAddFormVisible(true); }; @@ -107,7 +110,7 @@ export const TotpEditor: React.FC = ({ const parsed = parseOtpAuthUri(scannedData); if (parsed && parsed.type === 'totp') { const secretKey = parsed.secret.replace(/\s/g, '').replace(/=+$/, ''); - const name = parsed.label || 'Authenticator'; + const name = parsed.label || ''; const newTotpCode: TotpCode = { Id: crypto.randomUUID().toUpperCase(), @@ -172,7 +175,8 @@ export const TotpEditor: React.FC = ({ throw new Error(t('totp.errors.invalidSecretKey')); } - return { secretKey, name: name || 'Authenticator' }; + // Name is optional; keep it blank when none was provided or derived. + return { secretKey, name }; }; /** @@ -182,6 +186,15 @@ export const TotpEditor: React.FC = ({ setIsAddFormVisible(false); setFormData({ name: '', secretKey: '' }); setFormError(null); + setShowNameField(false); + }; + + /** + * Hides the optional name field again and clears any entered value + */ + const hideNameField = (): void => { + setFormData({ ...formData, name: '' }); + setShowNameField(false); }; /** @@ -264,7 +277,9 @@ export 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); setShowQrCode(false); setIsEditModalOpen(true); @@ -277,10 +292,20 @@ export const TotpEditor: React.FC = ({ setIsEditModalOpen(false); setEditingTotpCode(null); setEditName(''); + setShowEditNameField(false); setEditSecret(''); setShowQrCode(false); }; + /** + * 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 */ @@ -289,9 +314,12 @@ export 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 ); @@ -393,6 +421,21 @@ export const TotpEditor: React.FC = ({ fontWeight: '600', marginTop: 12, }, + addNameLink: { + color: colors.primary, + fontSize: 14, + fontWeight: '600', + marginTop: 16, + }, + nameLabelRow: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + }, + removeNameButton: { + marginTop: 12, + padding: 4, + }, modalButtons: { flexDirection: 'row', gap: 12, @@ -533,7 +576,7 @@ export const TotpEditor: React.FC = ({ - {totpCode.Name} + {totpCode.Name || t('totp.defaultName')} {t('totp.saveToViewCode')} @@ -550,7 +593,7 @@ export const TotpEditor: React.FC = ({ style={styles.deleteButton} onPress={() => initiateTotpDelete(totpCode)} > - + @@ -655,18 +698,6 @@ export const TotpEditor: React.FC = ({ {t('totp.instructions')} - - {t('totp.nameOptional')} - - setFormData({ ...formData, name: text })} - autoCapitalize="words" - /> - {t('totp.secretKey')} @@ -677,6 +708,7 @@ export const TotpEditor: React.FC = ({ onChangeText={(text) => setFormData({ ...formData, secretKey: text })} autoCapitalize="characters" autoCorrect={false} + autoFocus multiline /> @@ -686,6 +718,35 @@ export const TotpEditor: React.FC = ({ )} + {/* Name is optional and hidden by default; revealed on demand since most codes don't need one. */} + {showNameField ? ( + <> + + + {t('totp.nameOptional')} + + + + + + setFormData({ ...formData, name: text })} + autoCapitalize="words" + autoFocus + /> + + ) : ( + setShowNameField(true)}> + + {t('totp.addName')} + + + )} + = ({ {editingTotpCode && ( - - {t('totp.nameOptional')} - - - @@ -781,6 +830,35 @@ export const TotpEditor: React.FC = ({ multiline /> + {/* Name is optional; hidden by default unless a custom name was set. */} + {showEditNameField ? ( + <> + + + {t('totp.nameOptional')} + + + + + + + + ) : ( + setShowEditNameField(true)}> + + {t('totp.addName')} + + + )} + {showQrCode && ( = ({ item }) : React.ReactN - {t('items.totpCode')} + {totpCode.Name || t('totp.defaultName')} {formatTotpCode(currentCodes[totpCode.Id])} diff --git a/apps/mobile-app/i18n/locales/en.json b/apps/mobile-app/i18n/locales/en.json index 97313ac14..41c198125 100644 --- a/apps/mobile-app/i18n/locales/en.json +++ b/apps/mobile-app/i18n/locales/en.json @@ -3,6 +3,7 @@ "cancel": "Cancel", "close": "Close", "delete": "Delete", + "remove": "Remove", "save": "Save", "edit": "Edit", "yes": "Yes", @@ -114,11 +115,13 @@ "totp": { "addCode": "Add 2FA Code", "nameOptional": "Name (optional)", + "addName": "Add a name (optional)", + "defaultName": "Authenticator", "secretKey": "Secret Key", "instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.", "saveToViewCode": "Save to view code", "scanQrCode": "Scan QR Code", - "enterManually": "Enter a setup key", + "enterManually": "Enter secret key", "errors": { "invalidSecretKey": "Invalid secret key format.", "scanFailed": "Failed to scan QR code. Please try again."