mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-08-01 09:47:18 -04:00
Update TOTP create to hide name input by default in browser extension (#2216)
This commit is contained in:
@@ -65,10 +65,10 @@ const ModalWrapper: React.FC<ModalWrapperProps> = ({
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<HTMLDivElement>): void => {
|
||||
// Only close if clicking directly on the container, not the modal content
|
||||
const handleContainerMouseDown = (e: React.MouseEvent<HTMLDivElement>): 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<ModalWrapperProps> = ({
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-80 transition-opacity"
|
||||
onClick={onClose}
|
||||
onMouseDown={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal container - clicking here (outside modal content) also closes */}
|
||||
{/* Modal container - pressing here (outside modal content) also closes */}
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center p-4"
|
||||
onClick={handleContainerClick}
|
||||
onMouseDown={handleContainerMouseDown}
|
||||
>
|
||||
<div className={`relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 shadow-xl transition-all w-full ${maxWidth} mx-4`}>
|
||||
{/* Header - only show as block if title exists */}
|
||||
|
||||
@@ -177,11 +177,11 @@ const TotpBlock: React.FC<TotpBlockProps> = ({ 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`}
|
||||
>
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<div className="flex items-center flex-1">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white">{totpCode.Name}</h4>
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white">{totpCode.Name || t('totp.defaultName')}</h4>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-col items-end">
|
||||
|
||||
@@ -44,9 +44,11 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
const [showNameField, setShowNameField] = useState(!!formData.name);
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [editingTotpCode, setEditingTotpCode] = useState<TotpCode | null>(null);
|
||||
const [editName, setEditName] = useState('');
|
||||
const [showEditNameField, setShowEditNameField] = useState(false);
|
||||
const [editSecret, setEditSecret] = useState('');
|
||||
const [showQrCode, setShowQrCode] = useState(false);
|
||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string | null>(null);
|
||||
@@ -84,7 +86,8 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
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<TotpEditorProps> = ({
|
||||
formData: { name: '', secretKey: '' }
|
||||
});
|
||||
setFormError(null);
|
||||
setShowNameField(false);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -107,6 +111,7 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
formData: { name: '', secretKey: '' }
|
||||
});
|
||||
setFormError(null);
|
||||
setShowNameField(false);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -119,6 +124,14 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<TotpEditorProps> = ({
|
||||
*/
|
||||
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<TotpEditorProps> = ({
|
||||
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<TotpEditorProps> = ({
|
||||
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<TotpEditorProps> = ({
|
||||
|
||||
{isAddFormVisible && (
|
||||
<div className="p-4 mb-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h4 className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
{t('totp.addCode')}
|
||||
</h4>
|
||||
@@ -342,19 +370,6 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="totp-name" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{t('totp.nameOptional')}
|
||||
</label>
|
||||
<input
|
||||
id="totp-name"
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="totp-secret" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{t('totp.secretKey')}
|
||||
@@ -362,6 +377,7 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
<input
|
||||
id="totp-secret"
|
||||
type="text"
|
||||
autoFocus
|
||||
value={formData.secretKey}
|
||||
onChange={(e) => updateFormData({ secretKey: e.target.value })}
|
||||
onKeyDown={(e) => {
|
||||
@@ -374,15 +390,55 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleAddTotpCode(e)}
|
||||
className="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
|
||||
>
|
||||
{t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
{/* Name is optional and hidden by default */}
|
||||
{showNameField ? (
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label htmlFor="totp-name" className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{t('totp.nameOptional')}
|
||||
</label>
|
||||
<button
|
||||
id="remove-totp-name"
|
||||
type="button"
|
||||
onClick={hideNameField}
|
||||
className="w-5 h-5 flex items-center justify-center text-gray-300 hover:text-red-400 dark:text-gray-500 dark:hover:text-red-400 transition-colors"
|
||||
title={t('common.remove')}
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
id="totp-name"
|
||||
type="text"
|
||||
autoFocus
|
||||
value={formData.name}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-4">
|
||||
<button
|
||||
id="add-totp-name"
|
||||
type="button"
|
||||
onClick={() => setShowNameField(true)}
|
||||
className="text-sm font-medium text-primary-700 hover:text-primary-800 dark:text-primary-500 dark:hover:text-primary-400"
|
||||
>
|
||||
{t('totp.addName')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleAddTotpCode(e)}
|
||||
className="w-full text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
|
||||
>
|
||||
{t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -396,7 +452,7 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<div className="flex items-center flex-1">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{totpCode.Name}
|
||||
{totpCode.Name || t('totp.defaultName')}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -442,19 +498,6 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
<div className="space-y-4">
|
||||
{editingTotpCode && (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
||||
{t('totp.nameOptional')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => 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')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
@@ -487,6 +530,43 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Name is optional; hidden by default unless a custom name was set. */}
|
||||
{showEditNameField ? (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{t('totp.nameOptional')}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={hideEditNameField}
|
||||
className="w-5 h-5 flex items-center justify-center text-gray-300 hover:text-red-400 dark:text-gray-500 dark:hover:text-red-400 transition-colors"
|
||||
title={t('common.remove')}
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={editName}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowEditNameField(true)}
|
||||
className="text-sm font-medium text-primary-700 hover:text-primary-800 dark:text-primary-500 dark:hover:text-primary-400"
|
||||
>
|
||||
{t('totp.addName')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={saveEditedTotpCode}
|
||||
@@ -505,7 +585,7 @@ const TotpEditor: React.FC<TotpEditorProps> = ({
|
||||
onClose={cancelDeleteTotpCode}
|
||||
onConfirm={confirmDeleteTotpCode}
|
||||
title={t('totp.deleteTotpCodeTitle')}
|
||||
message={t('totp.deleteTotpCodeConfirmation', { name: totpToDelete?.Name })}
|
||||
message={t('totp.deleteTotpCodeConfirmation', { name: totpToDelete?.Name || t('totp.defaultName') })}
|
||||
confirmText={t('common.delete')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user