feat: add loading state management for TOTP and import/export operations

This commit is contained in:
shuaiplus
2026-04-09 23:27:40 +08:00
parent a982a5a57b
commit 2230f75d8a
6 changed files with 131 additions and 8 deletions
+26 -1
View File
@@ -172,9 +172,11 @@ export default function App() {
const [pendingTotp, setPendingTotp] = useState<PendingTotp | null>(null); const [pendingTotp, setPendingTotp] = useState<PendingTotp | null>(null);
const [totpCode, setTotpCode] = useState(''); const [totpCode, setTotpCode] = useState('');
const [rememberDevice, setRememberDevice] = useState(true); const [rememberDevice, setRememberDevice] = useState(true);
const [totpSubmitting, setTotpSubmitting] = useState(false);
const [disableTotpOpen, setDisableTotpOpen] = useState(false); const [disableTotpOpen, setDisableTotpOpen] = useState(false);
const [disableTotpPassword, setDisableTotpPassword] = useState(''); const [disableTotpPassword, setDisableTotpPassword] = useState('');
const [disableTotpSubmitting, setDisableTotpSubmitting] = useState(false);
const [recoverValues, setRecoverValues] = useState({ email: '', password: '', recoveryCode: '' }); const [recoverValues, setRecoverValues] = useState({ email: '', password: '', recoveryCode: '' });
const [themePreference, setThemePreference] = useState<ThemePreference>(() => readThemePreference()); const [themePreference, setThemePreference] = useState<ThemePreference>(() => readThemePreference());
const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>(() => resolveSystemTheme()); const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>(() => resolveSystemTheme());
@@ -433,16 +435,20 @@ export default function App() {
} }
async function handleTotpVerify() { async function handleTotpVerify() {
if (totpSubmitting) return;
if (!pendingTotp) return; if (!pendingTotp) return;
if (!totpCode.trim()) { if (!totpCode.trim()) {
pushToast('error', t('txt_please_input_totp_code')); pushToast('error', t('txt_please_input_totp_code'));
return; return;
} }
setTotpSubmitting(true);
try { try {
const login = await performTotpLogin(pendingTotp, totpCode, rememberDevice); const login = await performTotpLogin(pendingTotp, totpCode, rememberDevice);
await finalizeLogin(login); await finalizeLogin(login);
} catch (error) { } catch (error) {
pushToast('error', error instanceof Error ? error.message : t('txt_totp_verify_failed')); pushToast('error', error instanceof Error ? error.message : t('txt_totp_verify_failed'));
} finally {
setTotpSubmitting(false);
} }
} }
@@ -631,11 +637,13 @@ export default function App() {
onConfirmTotp={() => {}} onConfirmTotp={() => {}}
onCancelTotp={() => {}} onCancelTotp={() => {}}
onUseRecoveryCode={() => {}} onUseRecoveryCode={() => {}}
totpSubmitting={false}
disableTotpOpen={false} disableTotpOpen={false}
disableTotpPassword="" disableTotpPassword=""
onDisableTotpPasswordChange={() => {}} onDisableTotpPasswordChange={() => {}}
onConfirmDisableTotp={() => {}} onConfirmDisableTotp={() => {}}
onCancelDisableTotp={() => {}} onCancelDisableTotp={() => {}}
disableTotpSubmitting={false}
/> />
); );
} }
@@ -1288,21 +1296,25 @@ export default function App() {
onRememberDeviceChange={setRememberDevice} onRememberDeviceChange={setRememberDevice}
onConfirmTotp={() => void handleTotpVerify()} onConfirmTotp={() => void handleTotpVerify()}
onCancelTotp={() => { onCancelTotp={() => {
if (totpSubmitting) return;
setPendingTotp(null); setPendingTotp(null);
setTotpCode(''); setTotpCode('');
setRememberDevice(true); setRememberDevice(true);
}} }}
onUseRecoveryCode={() => { onUseRecoveryCode={() => {
if (totpSubmitting) return;
setPendingTotp(null); setPendingTotp(null);
setTotpCode(''); setTotpCode('');
setRememberDevice(true); setRememberDevice(true);
navigate('/recover-2fa'); navigate('/recover-2fa');
}} }}
totpSubmitting={totpSubmitting}
disableTotpOpen={false} disableTotpOpen={false}
disableTotpPassword="" disableTotpPassword=""
onDisableTotpPasswordChange={() => {}} onDisableTotpPasswordChange={() => {}}
onConfirmDisableTotp={() => {}} onConfirmDisableTotp={() => {}}
onCancelDisableTotp={() => {}} onCancelDisableTotp={() => {}}
disableTotpSubmitting={false}
/> />
</> </>
); );
@@ -1341,14 +1353,27 @@ export default function App() {
onConfirmTotp={() => {}} onConfirmTotp={() => {}}
onCancelTotp={() => {}} onCancelTotp={() => {}}
onUseRecoveryCode={() => {}} onUseRecoveryCode={() => {}}
totpSubmitting={false}
disableTotpOpen={disableTotpOpen} disableTotpOpen={disableTotpOpen}
disableTotpPassword={disableTotpPassword} disableTotpPassword={disableTotpPassword}
onDisableTotpPasswordChange={setDisableTotpPassword} onDisableTotpPasswordChange={setDisableTotpPassword}
onConfirmDisableTotp={() => void accountSecurityActions.disableTotp()} onConfirmDisableTotp={() => {
if (disableTotpSubmitting) return;
void (async () => {
setDisableTotpSubmitting(true);
try {
await accountSecurityActions.disableTotp();
} finally {
setDisableTotpSubmitting(false);
}
})();
}}
onCancelDisableTotp={() => { onCancelDisableTotp={() => {
if (disableTotpSubmitting) return;
setDisableTotpOpen(false); setDisableTotpOpen(false);
setDisableTotpPassword(''); setDisableTotpPassword('');
}} }}
disableTotpSubmitting={disableTotpSubmitting}
/> />
</> </>
); );
+7 -1
View File
@@ -27,11 +27,13 @@ interface AppGlobalOverlaysProps {
onConfirmTotp: () => void; onConfirmTotp: () => void;
onCancelTotp: () => void; onCancelTotp: () => void;
onUseRecoveryCode: () => void; onUseRecoveryCode: () => void;
totpSubmitting: boolean;
disableTotpOpen: boolean; disableTotpOpen: boolean;
disableTotpPassword: string; disableTotpPassword: string;
onDisableTotpPasswordChange: (value: string) => void; onDisableTotpPasswordChange: (value: string) => void;
onConfirmDisableTotp: () => void; onConfirmDisableTotp: () => void;
onCancelDisableTotp: () => void; onCancelDisableTotp: () => void;
disableTotpSubmitting: boolean;
} }
export default function AppGlobalOverlays(props: AppGlobalOverlaysProps) { export default function AppGlobalOverlays(props: AppGlobalOverlaysProps) {
@@ -57,12 +59,14 @@ export default function AppGlobalOverlays(props: AppGlobalOverlaysProps) {
confirmText={t('txt_verify')} confirmText={t('txt_verify')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
showIcon={false} showIcon={false}
confirmDisabled={props.totpSubmitting}
cancelDisabled={props.totpSubmitting}
onConfirm={props.onConfirmTotp} onConfirm={props.onConfirmTotp}
onCancel={props.onCancelTotp} onCancel={props.onCancelTotp}
afterActions={( afterActions={(
<div className="dialog-extra"> <div className="dialog-extra">
<div className="dialog-divider" /> <div className="dialog-divider" />
<button type="button" className="btn btn-secondary dialog-btn" onClick={props.onUseRecoveryCode}> <button type="button" className="btn btn-secondary dialog-btn" disabled={props.totpSubmitting} onClick={props.onUseRecoveryCode}>
{t('txt_use_recovery_code')} {t('txt_use_recovery_code')}
</button> </button>
</div> </div>
@@ -86,6 +90,8 @@ export default function AppGlobalOverlays(props: AppGlobalOverlaysProps) {
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
danger danger
showIcon={false} showIcon={false}
confirmDisabled={props.disableTotpSubmitting}
cancelDisabled={props.disableTotpSubmitting}
onConfirm={props.onConfirmDisableTotp} onConfirm={props.onConfirmDisableTotp}
onCancel={props.onCancelDisableTotp} onCancel={props.onCancelDisableTotp}
> >
@@ -528,6 +528,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
allowChecksumMismatch: boolean = false, allowChecksumMismatch: boolean = false,
knownIntegrity?: BackupFileIntegrityCheckResult knownIntegrity?: BackupFileIntegrityCheckResult
) { ) {
if (importing) return;
if (!selectedFile) { if (!selectedFile) {
const message = t('txt_backup_file_required'); const message = t('txt_backup_file_required');
setLocalError(message); setLocalError(message);
@@ -654,6 +655,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
} }
async function handleDeleteRemote(path: string) { async function handleDeleteRemote(path: string) {
if (deletingRemotePath) return;
if (!savedSelectedDestination) return; if (!savedSelectedDestination) return;
setDeletingRemotePath(path); setDeletingRemotePath(path);
setLocalError(''); setLocalError('');
@@ -723,6 +725,7 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
allowChecksumMismatch: boolean = false, allowChecksumMismatch: boolean = false,
knownIntegrity?: BackupFileIntegrityCheckResult knownIntegrity?: BackupFileIntegrityCheckResult
) { ) {
if (restoringRemotePath) return;
if (!savedSelectedDestination) return; if (!savedSelectedDestination) return;
setConfirmRemoteReplaceOpen(false); setConfirmRemoteReplaceOpen(false);
setConfirmIntegrityWarningOpen(false); setConfirmIntegrityWarningOpen(false);
@@ -896,9 +899,12 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
message={selectedFile ? t('txt_backup_selected_file_name', { name: selectedFile.name }) : t('txt_backup_restore_note')} message={selectedFile ? t('txt_backup_selected_file_name', { name: selectedFile.name }) : t('txt_backup_restore_note')}
confirmText={t('txt_backup_import')} confirmText={t('txt_backup_import')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
confirmDisabled={importing}
cancelDisabled={importing}
danger danger
onConfirm={() => void runLocalRestore(false)} onConfirm={() => void runLocalRestore(false)}
onCancel={() => { onCancel={() => {
if (importing) return;
setConfirmLocalRestoreOpen(false); setConfirmLocalRestoreOpen(false);
resetSelectedFile(); resetSelectedFile();
resetPendingIntegrityWarning(); resetPendingIntegrityWarning();
@@ -959,6 +965,8 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
variant="warning" variant="warning"
confirmText={t('txt_backup_restore_checksum_warning_confirm')} confirmText={t('txt_backup_restore_checksum_warning_confirm')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
confirmDisabled={importing || !!restoringRemotePath}
cancelDisabled={importing || !!restoringRemotePath}
danger danger
onConfirm={() => { onConfirm={() => {
if (!pendingRestoreIntegrity) return; if (!pendingRestoreIntegrity) return;
@@ -984,6 +992,8 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
message={t('txt_backup_remote_delete_confirm_message', { name: pendingRemoteDeletePath.split('/').pop() || pendingRemoteDeletePath })} message={t('txt_backup_remote_delete_confirm_message', { name: pendingRemoteDeletePath.split('/').pop() || pendingRemoteDeletePath })}
confirmText={t('txt_delete')} confirmText={t('txt_delete')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
confirmDisabled={!!deletingRemotePath}
cancelDisabled={!!deletingRemotePath}
danger danger
onConfirm={() => void handleDeleteRemote(pendingRemoteDeletePath)} onConfirm={() => void handleDeleteRemote(pendingRemoteDeletePath)}
onCancel={() => { onCancel={() => {
@@ -1001,6 +1011,8 @@ export default function BackupCenterPage(props: BackupCenterPageProps) {
})} })}
confirmText={t('txt_delete')} confirmText={t('txt_delete')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
confirmDisabled={savingSettings}
cancelDisabled={savingSettings}
danger danger
onConfirm={() => void handleDeleteDestination()} onConfirm={() => void handleDeleteDestination()}
onCancel={() => { onCancel={() => {
+9
View File
@@ -468,6 +468,7 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
} }
async function handlePasswordImportConfirm() { async function handlePasswordImportConfirm() {
if (isPasswordSubmitting) return;
if (!pendingPasswordImport) return; if (!pendingPasswordImport) return;
setIsPasswordSubmitting(true); setIsPasswordSubmitting(true);
try { try {
@@ -486,6 +487,7 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
} }
async function handleZipPasswordImportConfirm() { async function handleZipPasswordImportConfirm() {
if (isZipPasswordSubmitting) return;
if (!pendingZipFile) return; if (!pendingZipFile) return;
setIsZipPasswordSubmitting(true); setIsZipPasswordSubmitting(true);
try { try {
@@ -558,6 +560,7 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
} }
async function handleExportConfirmPassword() { async function handleExportConfirmPassword() {
if (isExporting) return;
const masterPassword = String(exportAuthPassword || '').trim(); const masterPassword = String(exportAuthPassword || '').trim();
if (!masterPassword) { if (!masterPassword) {
onNotify('error', t('txt_master_password_is_required')); onNotify('error', t('txt_master_password_is_required'));
@@ -736,6 +739,8 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
confirmText={isExporting ? t('txt_loading') : t('txt_verify')} confirmText={isExporting ? t('txt_loading') : t('txt_verify')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
showIcon={false} showIcon={false}
confirmDisabled={isExporting}
cancelDisabled={isExporting}
onConfirm={() => void handleExportConfirmPassword()} onConfirm={() => void handleExportConfirmPassword()}
onCancel={() => { onCancel={() => {
if (isExporting) return; if (isExporting) return;
@@ -761,6 +766,8 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
confirmText={isPasswordSubmitting ? t('txt_loading') : t('txt_import')} confirmText={isPasswordSubmitting ? t('txt_loading') : t('txt_import')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
showIcon={false} showIcon={false}
confirmDisabled={isPasswordSubmitting}
cancelDisabled={isPasswordSubmitting}
onConfirm={() => void handlePasswordImportConfirm()} onConfirm={() => void handlePasswordImportConfirm()}
onCancel={() => { onCancel={() => {
if (isPasswordSubmitting) return; if (isPasswordSubmitting) return;
@@ -787,6 +794,8 @@ export default function ImportPage({ onImport, onImportEncryptedRaw, accountKeys
confirmText={isZipPasswordSubmitting ? t('txt_loading') : t('txt_import')} confirmText={isZipPasswordSubmitting ? t('txt_loading') : t('txt_import')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
showIcon={false} showIcon={false}
confirmDisabled={isZipPasswordSubmitting}
cancelDisabled={isZipPasswordSubmitting}
onConfirm={() => void handleZipPasswordImportConfirm()} onConfirm={() => void handleZipPasswordImportConfirm()}
onCancel={() => { onCancel={() => {
if (isZipPasswordSubmitting) return; if (isZipPasswordSubmitting) return;
+1
View File
@@ -1009,6 +1009,7 @@ function folderName(id: string | null | undefined): string {
</div> </div>
<VaultDialogs <VaultDialogs
busy={busy}
fieldModalOpen={fieldModalOpen} fieldModalOpen={fieldModalOpen}
fieldType={fieldType} fieldType={fieldType}
fieldLabel={fieldLabel} fieldLabel={fieldLabel}
+76 -6
View File
@@ -4,6 +4,7 @@ import { FIELD_TYPE_OPTIONS, toBooleanFieldValue } from '@/components/vault/vaul
import { t } from '@/lib/i18n'; import { t } from '@/lib/i18n';
interface VaultDialogsProps { interface VaultDialogsProps {
busy: boolean;
fieldModalOpen: boolean; fieldModalOpen: boolean;
fieldType: CustomFieldType; fieldType: CustomFieldType;
fieldLabel: string; fieldLabel: string;
@@ -108,6 +109,8 @@ export default function VaultDialogs(props: VaultDialogsProps) {
message={t('txt_archive_item_message')} message={t('txt_archive_item_message')}
confirmText={t('txt_archive')} confirmText={t('txt_archive')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmArchive} onConfirm={props.onConfirmArchive}
onCancel={props.onCancelArchive} onCancel={props.onCancelArchive}
/> />
@@ -118,11 +121,22 @@ export default function VaultDialogs(props: VaultDialogsProps) {
message={t('txt_archive_selected_items_message', { count: props.selectedCount })} message={t('txt_archive_selected_items_message', { count: props.selectedCount })}
confirmText={t('txt_archive')} confirmText={t('txt_archive')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmBulkArchive} onConfirm={props.onConfirmBulkArchive}
onCancel={props.onCancelBulkArchive} onCancel={props.onCancelBulkArchive}
/> />
<ConfirmDialog open={props.pendingDeleteOpen} title={t('txt_delete_item')} message={t('txt_are_you_sure_you_want_to_delete_this_item')} danger onConfirm={props.onConfirmDelete} onCancel={props.onCancelDelete} /> <ConfirmDialog
open={props.pendingDeleteOpen}
title={t('txt_delete_item')}
message={t('txt_are_you_sure_you_want_to_delete_this_item')}
danger
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmDelete}
onCancel={props.onCancelDelete}
/>
<ConfirmDialog <ConfirmDialog
open={props.bulkDeleteOpen} open={props.bulkDeleteOpen}
@@ -133,11 +147,23 @@ export default function VaultDialogs(props: VaultDialogsProps) {
: t('txt_are_you_sure_you_want_to_delete_count_selected_items', { count: props.selectedCount }) : t('txt_are_you_sure_you_want_to_delete_count_selected_items', { count: props.selectedCount })
} }
danger danger
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmBulkDelete} onConfirm={props.onConfirmBulkDelete}
onCancel={props.onCancelBulkDelete} onCancel={props.onCancelBulkDelete}
/> />
<ConfirmDialog open={props.moveOpen} title={t('txt_move_selected_items')} message={t('txt_choose_destination_folder')} confirmText={t('txt_move')} cancelText={t('txt_cancel')} onConfirm={props.onConfirmMove} onCancel={props.onCancelMove}> <ConfirmDialog
open={props.moveOpen}
title={t('txt_move_selected_items')}
message={t('txt_choose_destination_folder')}
confirmText={t('txt_move')}
cancelText={t('txt_cancel')}
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmMove}
onCancel={props.onCancelMove}
>
<label className="field"> <label className="field">
<span>{t('txt_folder')}</span> <span>{t('txt_folder')}</span>
<select className="input" value={props.moveFolderId} onInput={(e) => props.onMoveFolderIdChange((e.currentTarget as HTMLSelectElement).value)}> <select className="input" value={props.moveFolderId} onInput={(e) => props.onMoveFolderIdChange((e.currentTarget as HTMLSelectElement).value)}>
@@ -151,14 +177,34 @@ export default function VaultDialogs(props: VaultDialogsProps) {
</label> </label>
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog open={props.createFolderOpen} title={t('txt_create_folder')} message={t('txt_enter_a_folder_name')} confirmText={t('txt_create')} cancelText={t('txt_cancel')} onConfirm={props.onConfirmCreateFolder} onCancel={props.onCancelCreateFolder}> <ConfirmDialog
open={props.createFolderOpen}
title={t('txt_create_folder')}
message={t('txt_enter_a_folder_name')}
confirmText={t('txt_create')}
cancelText={t('txt_cancel')}
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmCreateFolder}
onCancel={props.onCancelCreateFolder}
>
<label className="field"> <label className="field">
<span>{t('txt_folder_name')}</span> <span>{t('txt_folder_name')}</span>
<input className="input" value={props.newFolderName} onInput={(e) => props.onNewFolderNameChange((e.currentTarget as HTMLInputElement).value)} /> <input className="input" value={props.newFolderName} onInput={(e) => props.onNewFolderNameChange((e.currentTarget as HTMLInputElement).value)} />
</label> </label>
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog open={props.renameFolderOpen} title={t('txt_edit')} message={t('txt_enter_a_folder_name')} confirmText={t('txt_save')} cancelText={t('txt_cancel')} onConfirm={props.onConfirmRenameFolder} onCancel={props.onCancelRenameFolder}> <ConfirmDialog
open={props.renameFolderOpen}
title={t('txt_edit')}
message={t('txt_enter_a_folder_name')}
confirmText={t('txt_save')}
cancelText={t('txt_cancel')}
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmRenameFolder}
onCancel={props.onCancelRenameFolder}
>
<label className="field"> <label className="field">
<span>{t('txt_folder_name')}</span> <span>{t('txt_folder_name')}</span>
<input className="input" value={props.renameFolderName} onInput={(e) => props.onRenameFolderNameChange((e.currentTarget as HTMLInputElement).value)} /> <input className="input" value={props.renameFolderName} onInput={(e) => props.onRenameFolderNameChange((e.currentTarget as HTMLInputElement).value)} />
@@ -172,13 +218,37 @@ export default function VaultDialogs(props: VaultDialogsProps) {
confirmText={t('txt_delete')} confirmText={t('txt_delete')}
cancelText={t('txt_cancel')} cancelText={t('txt_cancel')}
danger danger
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmDeleteFolder} onConfirm={props.onConfirmDeleteFolder}
onCancel={props.onCancelDeleteFolder} onCancel={props.onCancelDeleteFolder}
/> />
<ConfirmDialog open={props.deleteAllFoldersOpen} title={t('txt_delete_all_folders')} message={t('txt_delete_all_folders_message')} confirmText={t('txt_delete')} cancelText={t('txt_cancel')} danger onConfirm={props.onConfirmDeleteAllFolders} onCancel={props.onCancelDeleteAllFolders} /> <ConfirmDialog
open={props.deleteAllFoldersOpen}
title={t('txt_delete_all_folders')}
message={t('txt_delete_all_folders_message')}
confirmText={t('txt_delete')}
cancelText={t('txt_cancel')}
danger
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmDeleteAllFolders}
onCancel={props.onCancelDeleteAllFolders}
/>
<ConfirmDialog open={props.repromptOpen} title={t('txt_unlock_item')} message={t('txt_enter_master_password_to_view_this_item')} confirmText={t('txt_unlock')} cancelText={t('txt_cancel')} showIcon={false} onConfirm={props.onConfirmReprompt} onCancel={props.onCancelReprompt}> <ConfirmDialog
open={props.repromptOpen}
title={t('txt_unlock_item')}
message={t('txt_enter_master_password_to_view_this_item')}
confirmText={t('txt_unlock')}
cancelText={t('txt_cancel')}
showIcon={false}
confirmDisabled={props.busy}
cancelDisabled={props.busy}
onConfirm={props.onConfirmReprompt}
onCancel={props.onCancelReprompt}
>
<label className="field"> <label className="field">
<span>{t('txt_master_password')}</span> <span>{t('txt_master_password')}</span>
<input className="input" type="password" value={props.repromptPassword} onInput={(e) => props.onRepromptPasswordChange((e.currentTarget as HTMLInputElement).value)} /> <input className="input" type="password" value={props.repromptPassword} onInput={(e) => props.onRepromptPasswordChange((e.currentTarget as HTMLInputElement).value)} />