From 3eb39c2968d37674f406f0384c09e4e2ebebd71c Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 14 Dec 2025 08:22:41 +0100 Subject: [PATCH] Update modals to use shared wrapper (#1404) --- .../Credentials/Details/FieldHistoryModal.tsx | 128 ++++----- .../components/Dialogs/ConfirmDeleteModal.tsx | 59 +++++ .../popup/components/Dialogs/HelpModal.tsx | 50 +--- .../components/Dialogs/MobileUnlockModal.tsx | 97 +++---- .../popup/components/Dialogs/Modal.tsx | 115 ++++---- .../popup/components/Dialogs/ModalWrapper.tsx | 134 ++++++++++ .../Dialogs/PasskeyBypassDialog.tsx | 64 ++--- .../Dialogs/PasswordConfigDialog.tsx | 247 ++++++++---------- .../components/Folders/DeleteFolderModal.tsx | 171 ++++++------ .../popup/components/Folders/FolderModal.tsx | 125 ++++----- .../popup/components/Forms/AddFieldMenu.tsx | 122 ++++----- .../pages/credentials/CredentialAddEdit.tsx | 4 +- .../popup/pages/credentials/ItemAddEdit.tsx | 4 +- .../popup/pages/items/RecentlyDeleted.tsx | 88 ++----- .../pages/passkeys/PasskeyAuthenticate.tsx | 13 +- .../popup/pages/passkeys/PasskeyCreate.tsx | 13 +- .../src/i18n/locales/en.json | 7 +- 17 files changed, 718 insertions(+), 723 deletions(-) create mode 100644 apps/browser-extension/src/entrypoints/popup/components/Dialogs/ConfirmDeleteModal.tsx create mode 100644 apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx diff --git a/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldHistoryModal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldHistoryModal.tsx index d5ffaf765..586c0ddfa 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldHistoryModal.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldHistoryModal.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; import { FormInputCopyToClipboard } from '@/entrypoints/popup/components/Forms/FormInputCopyToClipboard'; import { useDb } from '@/entrypoints/popup/context/DbContext'; @@ -56,10 +57,6 @@ const FieldHistoryModal: React.FC = ({ } }, [isOpen, dbContext?.sqliteClient, itemId, fieldKey]); - if (!isOpen) { - return null; - } - /** * Format a date string to a human readable format. */ @@ -89,87 +86,52 @@ const FieldHistoryModal: React.FC = ({ } }; - /** - * Handle click on backdrop to close modal. - */ - const handleBackdropClick = (e: React.MouseEvent): void => { - // Only close if clicking directly on the backdrop/container, not the modal content - if (e.target === e.currentTarget) { - onClose(); - } - }; - return ( -
- {/* Backdrop */} -
- - {/* Modal container - clicking here (outside modal content) closes */} -
-
- {/* Header */} -
-

- {fieldLabel} {t('credentials.history')} -

- -
- - {/* Content */} -
- {loading ? ( -
-
-
- ) : history.length === 0 ? ( -
- {t('credentials.noHistoryAvailable')} -
- ) : ( -
- {history.map((record) => { - const values = parseValueSnapshot(record.ValueSnapshot); - - return ( -
-
- {formatDate(record.ChangedAt)} -
- - {values.map((value, idx) => ( -
- -
- ))} -
- ); - })} -
- )} -
+ + {loading ? ( +
+
-
-
+ ) : history.length === 0 ? ( +
+ {t('credentials.noHistoryAvailable')} +
+ ) : ( +
+ {history.map((record) => { + const values = parseValueSnapshot(record.ValueSnapshot); + + return ( +
+
+ {formatDate(record.ChangedAt)} +
+ + {values.map((value, idx) => ( +
+ +
+ ))} +
+ ); + })} +
+ )} + ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ConfirmDeleteModal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ConfirmDeleteModal.tsx new file mode 100644 index 000000000..bf591a073 --- /dev/null +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ConfirmDeleteModal.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; + +type ConfirmDeleteModalProps = { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; + message: string; + confirmText: string; +} + +/** + * A reusable confirmation modal for delete actions. + * Uses the danger styling to indicate a destructive action. + */ +const ConfirmDeleteModal: React.FC = ({ + isOpen, + onClose, + onConfirm, + title, + message, + confirmText +}) => { + const { t } = useTranslation(); + + return ( + + + +
+ } + > +

+ {message} +

+ + ); +}; + +export default ConfirmDeleteModal; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/HelpModal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/HelpModal.tsx index f24be5456..8cc512a57 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/HelpModal.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/HelpModal.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; + type HelpModalProps = { title: string; content: string; @@ -38,45 +40,15 @@ const HelpModal: React.FC = ({ title, content, className = '' }) - {showModal && ( -
-
-
-

- {title} -

- -
-

- {content} -

- -
-
- )} + setShowModal(false)} + title={title} + > +

+ {content} +

+
); }; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/MobileUnlockModal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/MobileUnlockModal.tsx index ca105b085..0c3e05683 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/MobileUnlockModal.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/MobileUnlockModal.tsx @@ -2,6 +2,7 @@ import QRCode from 'qrcode'; import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; import { MobileLoginErrorCode } from '@/entrypoints/popup/types/MobileLoginErrorCode'; import { MobileLoginUtility } from '@/entrypoints/popup/utils/MobileLoginUtility'; @@ -170,74 +171,50 @@ const MobileUnlockModal: React.FC = ({ return `${mins}:${secs.toString().padStart(2, '0')}`; }; - if (!isOpen) { - return null; - } - const title = mode === 'unlock' ? t('auth.unlockWithMobile') : t('auth.loginWithMobile'); const description = t('auth.scanQrCode'); return ( -
- {/* Backdrop */} -
+ +

+ {description} +

- {/* Modal */} -
-
- {/* Close button */} - + {error && ( +
+ {getErrorMessage(error)} +
+ )} - {/* Content */} -
-

- {title} -

-

- {description} -

- - {error && ( -
- {getErrorMessage(error)} -
- )} - - {qrCodeUrl && ( -
- QR Code -
- {formatTime(timeRemaining)} -
-
- )} - - {!qrCodeUrl && !error && ( -
-
-
- )} - - + {qrCodeUrl && ( +
+ QR Code +
+ {formatTime(timeRemaining)}
-
-
+ )} + + {!qrCodeUrl && !error && ( +
+
+
+ )} + + + ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/Modal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/Modal.tsx index eac619123..85caca2b5 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/Modal.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/Modal.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; + +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; interface IModalProps { isOpen: boolean; @@ -14,6 +15,7 @@ interface IModalProps { /** * A reusable modal component for confirmations and alerts. + * Built on top of ModalWrapper for consistent behavior. */ const Modal: React.FC = ({ isOpen, @@ -25,81 +27,62 @@ const Modal: React.FC = ({ cancelText = '', variant = 'default' }) => { - const { t } = useTranslation(); - if (!isOpen) { - return null; - } - const confirmButtonClass = variant === 'danger' ? 'bg-red-600 hover:bg-red-700 focus:ring-red-500' : 'bg-primary-600 hover:bg-primary-700 focus:ring-primary-500'; return ( -
- {/* Backdrop */} -
- - {/* Modal */} -
-
- {/* Close button */} - - - {/* Content */} -
- {variant === 'danger' && ( -
- - - -
- )} -
-

- {title} -

-
-

- {message} -

-
-
- - {/* Actions */} -
- {confirmText && ( - - )} - {cancelText && ( - - )} + )} +
+

+ {title} +

+
+

+ {message} +

-
+ + {/* Actions */} +
+ {confirmText && ( + + )} + {cancelText && ( + + )} +
+ ); }; -export default Modal; \ No newline at end of file +export default Modal; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx new file mode 100644 index 000000000..f599f0a87 --- /dev/null +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/ModalWrapper.tsx @@ -0,0 +1,134 @@ +import React, { useEffect, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +type ModalWrapperProps = { + isOpen: boolean; + onClose: () => void; + /** Modal title (optional - if not provided, no header is shown) */ + title?: string; + children: React.ReactNode; + /** Optional max-width class (default: 'max-w-md') */ + maxWidth?: string; + /** Whether to show the close button in header (default: true) */ + showCloseButton?: boolean; + /** Optional footer actions */ + footer?: React.ReactNode; + /** Whether to show header border (default: true) */ + showHeaderBorder?: boolean; + /** Custom body padding class (default: 'px-6 py-4') */ + bodyClassName?: string; +}; + +/** + * A generic modal wrapper component that provides consistent behavior: + * - Click outside to close (on backdrop) + * - Escape key to close + * - Dark overlay background + * - Consistent styling and animations + */ +const ModalWrapper: React.FC = ({ + isOpen, + onClose, + title, + children, + maxWidth = 'max-w-md', + showCloseButton = true, + footer, + showHeaderBorder = true, + bodyClassName = 'px-6 py-4' +}) => { + const { t } = useTranslation(); + + /** + * Handle escape key press to close modal. + */ + const handleKeyDown = useCallback((e: KeyboardEvent): void => { + if (e.key === 'Escape') { + onClose(); + } + }, [onClose]); + + /** + * Add/remove escape key listener when modal opens/closes. + */ + useEffect(() => { + if (isOpen) { + document.addEventListener('keydown', handleKeyDown); + return (): void => { + document.removeEventListener('keydown', handleKeyDown); + }; + } + }, [isOpen, handleKeyDown]); + + if (!isOpen) { + return null; + } + + /** + * Handle click 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 + if (e.target === e.currentTarget) { + onClose(); + } + }; + + const hasHeader = title || showCloseButton; + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal container - clicking here (outside modal content) also closes */} +
+
+ {/* Header */} + {hasHeader && ( +
+ {title && ( +

+ {title} +

+ )} + {!title &&
} + {showCloseButton && ( + + )} +
+ )} + + {/* Body */} +
+ {children} +
+ + {/* Footer */} + {footer && ( +
+ {footer} +
+ )} +
+
+
+ ); +}; + +export default ModalWrapper; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasskeyBypassDialog.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasskeyBypassDialog.tsx index 6201d52f5..86cacbef6 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasskeyBypassDialog.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasskeyBypassDialog.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import Button from '../Button'; +import Button from '@/entrypoints/popup/components/Button'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; type PasskeyBypassDialogProps = { + isOpen: boolean; origin: string; onChoice: (choice: 'once' | 'always') => void; onCancel: () => void; @@ -13,6 +15,7 @@ type PasskeyBypassDialogProps = { * Dialog for choosing how to bypass AliasVault passkey provider */ const PasskeyBypassDialog: React.FC = ({ + isOpen, origin, onChoice, onCancel @@ -20,40 +23,39 @@ const PasskeyBypassDialog: React.FC = ({ const { t } = useTranslation(); return ( -
-
-

- {t('passkeys.bypass.title')} -

+ +

+ {t('passkeys.bypass.description', { origin })} +

-

- {t('passkeys.bypass.description', { origin })} -

+
+ -
- + - - - -
+
-
+ ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasswordConfigDialog.tsx b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasswordConfigDialog.tsx index a8c2c2f75..7a1cb5d44 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasswordConfigDialog.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Dialogs/PasswordConfigDialog.tsx @@ -1,6 +1,8 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; + import type { PasswordSettings } from '@/utils/dist/core/models/vault'; import { CreatePasswordGenerator } from '@/utils/dist/core/password-generator'; @@ -61,158 +63,127 @@ const PasswordConfigDialog: React.FC = ({ onClose(); }, [previewPassword, onSave, onClose]); - const handleCancel = useCallback(() => { - onClose(); - }, [onClose]); - - if (!isOpen) { - return null; - } - return ( -
- {/* Backdrop */} -
- - {/* Modal */} -
-
- {/* Close button */} + +
+ } + > +
+ {/* Password Preview */} +
+
+ + +
+
- {/* Modal content */} -
-
-

- {t('credentials.changePasswordComplexity')} -

+ {/* Character Type Toggle Buttons */} +
+
+ {/* Lowercase Toggle */} + -
- {/* Password Preview */} -
-
- - -
-
+ {/* Uppercase Toggle */} + - {/* Character Type Toggle Buttons */} -
-
- {/* Lowercase Toggle */} - + {/* Numbers Toggle */} + - {/* Uppercase Toggle */} - + {/* Special Characters Toggle */} + +
- {/* Numbers Toggle */} - - - {/* Special Characters Toggle */} - -
- - {/* Avoid Ambiguous Characters - Checkbox */} -
- handleSettingChange('UseNonAmbiguousChars', e.target.checked)} - className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600" - /> - -
-
-
- - {/* Action buttons */} -
- -
-
+ {/* Avoid Ambiguous Characters - Checkbox */} +
+ handleSettingChange('UseNonAmbiguousChars', e.target.checked)} + className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600" + /> +
-
+ ); }; -export default PasswordConfigDialog; \ No newline at end of file +export default PasswordConfigDialog; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Folders/DeleteFolderModal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Folders/DeleteFolderModal.tsx index bdcc8a8b6..3855ee39d 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Folders/DeleteFolderModal.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Folders/DeleteFolderModal.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; + type DeleteFolderModalProps = { isOpen: boolean; onClose: () => void; @@ -55,112 +57,99 @@ const DeleteFolderModal: React.FC = ({ }; /** - * Handle escape key press + * Handle close - only allow if not submitting */ - const handleKeyDown = (e: React.KeyboardEvent): void => { - if (e.key === 'Escape' && !isSubmitting) { + const handleClose = (): void => { + if (!isSubmitting) { onClose(); } }; - if (!isOpen) { - return null; - } - return ( -
-
- {/* Header */} -
-

- {t('items.deleteFolder')} -

-
- - {/* Body */} -
-

- {t('items.deleteFolderConfirm', { folderName })} -

- - {itemCount > 0 && ( -

- {t('items.folderContainsItems', { count: itemCount })} -

- )} - - {/* Option buttons */} -
- {/* Delete folder only - move items to root */} - - - {/* Delete folder and contents */} - {itemCount > 0 && ( - - )} -
-
- - {/* Footer */} -
+
+ } + > +
+

+ {t('items.deleteFolderConfirm', { folderName })} +

+ + {itemCount > 0 && ( +

+ {t('items.folderContainsItems', { count: itemCount })} +

+ )} + + {/* Option buttons */} +
+ {/* Delete folder only - move items to root */} + + + {/* Delete folder and contents */} + {itemCount > 0 && ( + + )} +
-
+ ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Folders/FolderModal.tsx b/apps/browser-extension/src/entrypoints/popup/components/Folders/FolderModal.tsx index cbc87c19b..084d87a02 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Folders/FolderModal.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Folders/FolderModal.tsx @@ -1,6 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; + type FolderModalProps = { isOpen: boolean; onClose: () => void; @@ -57,81 +59,58 @@ const FolderModal: React.FC = ({ } }; - /** - * Handle the escape key press. - */ - const handleKeyDown = (e: React.KeyboardEvent): void => { - if (e.key === 'Escape') { - onClose(); - } - }; - - if (!isOpen) { - return null; - } - return ( -
-
- {/* Header */} -
-

- {mode === 'create' ? t('items.createFolder') : t('items.editFolder')} -

+ + +
- - {/* Body */} -
-
- - setFolderName(e.target.value)} - placeholder={t('items.folderNamePlaceholder')} - autoFocus - className="w-full p-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-blue-500 focus:border-blue-500" - /> - {error && ( -

{error}

- )} -
- - {/* Footer */} -
- - -
-
-
-
+ } + > +
+ + setFolderName(e.target.value)} + placeholder={t('items.folderNamePlaceholder')} + autoFocus + className="w-full p-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-blue-500 focus:border-blue-500" + /> + {error && ( +

{error}

+ )} +
+ ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/components/Forms/AddFieldMenu.tsx b/apps/browser-extension/src/entrypoints/popup/components/Forms/AddFieldMenu.tsx index ce0ab58a6..28a91f0b4 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Forms/AddFieldMenu.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Forms/AddFieldMenu.tsx @@ -1,6 +1,8 @@ import React, { useState, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import ModalWrapper from '@/entrypoints/popup/components/Dialogs/ModalWrapper'; + import type { FieldType } from '@/utils/dist/core/models/vault'; /** @@ -204,8 +206,9 @@ const AddFieldMenu: React.FC = ({ {/* Dropdown Menu */} {isOpen && ( <> + {/* Dark overlay backdrop for better visibility */}
setIsOpen(false)} />
@@ -239,69 +242,66 @@ const AddFieldMenu: React.FC = ({
{/* Custom Field Modal */} - {showCustomFieldModal && ( -
-
-

- {t('itemTypes.addCustomField')} -

+ + + +
+ } + > +
+
+ + setCustomFieldLabel(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white" + placeholder={t('itemTypes.enterFieldName')} + autoFocus + /> +
-
-
- - setCustomFieldLabel(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white" - placeholder={t('itemTypes.enterFieldName')} - autoFocus - /> -
- -
- - -
-
- -
- - -
+
+ +
- )} + ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/pages/credentials/CredentialAddEdit.tsx b/apps/browser-extension/src/entrypoints/popup/pages/credentials/CredentialAddEdit.tsx index 09689af3a..8e81ae315 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/credentials/CredentialAddEdit.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/credentials/CredentialAddEdit.tsx @@ -661,8 +661,8 @@ const CredentialAddEdit: React.FC = () => { setShowDeleteModal(false); void handleDelete(); }} - title={t('credentials.deleteCredentialTitle')} - message={t('credentials.deleteCredentialConfirm')} + title={t('credentials.deleteItemTitle')} + message={t('credentials.deleteItemConfirm')} confirmText={t('common.delete')} cancelText={t('common.cancel')} variant="danger" diff --git a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx index eb5c752bf..cb5926482 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx @@ -1387,8 +1387,8 @@ const ItemAddEdit: React.FC = () => { setShowDeleteModal(false)} - title={t('credentials.deleteCredentialTitle')} - message={t('credentials.deleteCredentialConfirmation')} + title={t('credentials.deleteItemTitle')} + message={t('credentials.deleteItemConfirm')} confirmText={t('common.delete')} cancelText={t('common.cancel')} onConfirm={handleDelete} diff --git a/apps/browser-extension/src/entrypoints/popup/pages/items/RecentlyDeleted.tsx b/apps/browser-extension/src/entrypoints/popup/pages/items/RecentlyDeleted.tsx index 961d46882..c8c25c28b 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/items/RecentlyDeleted.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/items/RecentlyDeleted.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import ConfirmDeleteModal from '@/entrypoints/popup/components/Dialogs/ConfirmDeleteModal'; import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner'; import { useDb } from '@/entrypoints/popup/context/DbContext'; import { useHeaderButtons } from '@/entrypoints/popup/context/HeaderButtonsContext'; @@ -70,19 +71,19 @@ const RecentlyDeleted: React.FC = () => { /** * Permanently delete an item. */ - const handlePermanentDelete = useCallback(async (itemId: string) => { - if (!dbContext?.sqliteClient) { + const handlePermanentDelete = useCallback(async () => { + if (!dbContext?.sqliteClient || !selectedItemId) { return; } await executeVaultMutationAsync(async () => { - await dbContext.sqliteClient!.permanentlyDeleteItem(itemId); + await dbContext.sqliteClient!.permanentlyDeleteItem(selectedItemId); }); loadItems(); setShowConfirmDelete(false); setSelectedItemId(null); - }, [dbContext?.sqliteClient, executeVaultMutationAsync, loadItems]); + }, [dbContext?.sqliteClient, executeVaultMutationAsync, loadItems, selectedItemId]); /** * Empty all items from Recently Deleted (permanent delete all). @@ -124,6 +125,14 @@ const RecentlyDeleted: React.FC = () => { load(); }, [dbContext?.sqliteClient, setIsLoading, loadItems]); + /** + * Handle closing the delete confirmation modal. + */ + const handleCloseDeleteModal = useCallback(() => { + setShowConfirmDelete(false); + setSelectedItemId(null); + }, []); + if (isLoading) { return (
@@ -221,63 +230,24 @@ const RecentlyDeleted: React.FC = () => { )} {/* Confirm Delete Modal */} - {showConfirmDelete && selectedItemId && ( -
-
-

- {t('recentlyDeleted.confirmDeleteTitle')} -

-

- {t('recentlyDeleted.confirmDeleteMessage')} -

-
- - -
-
-
- )} + {/* Confirm Empty All Modal */} - {showConfirmEmptyAll && ( -
-
-

- {t('recentlyDeleted.confirmEmptyAllTitle')} -

-

- {t('recentlyDeleted.confirmEmptyAllMessage', { count: items.length })} -

-
- - -
-
-
- )} + setShowConfirmEmptyAll(false)} + onConfirm={handleEmptyAll} + title={t('recentlyDeleted.confirmEmptyAllTitle')} + message={t('recentlyDeleted.confirmEmptyAllMessage', { count: items.length })} + confirmText={t('recentlyDeleted.emptyAll')} + />
); }; diff --git a/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyAuthenticate.tsx b/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyAuthenticate.tsx index 4c5b1ddd7..8c3556272 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyAuthenticate.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyAuthenticate.tsx @@ -365,13 +365,12 @@ const PasskeyAuthenticate: React.FC = () => { return ( <> - {showBypassDialog && request && ( - setShowBypassDialog(false)} - /> - )} + setShowBypassDialog(false)} + />
diff --git a/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx b/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx index 97af8b764..7f4f1b48e 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/passkeys/PasskeyCreate.tsx @@ -546,13 +546,12 @@ const PasskeyCreate: React.FC = () => { return ( <> - {showBypassDialog && request && ( - setShowBypassDialog(false)} - /> - )} + setShowBypassDialog(false)} + /> {localLoading && (
diff --git a/apps/browser-extension/src/i18n/locales/en.json b/apps/browser-extension/src/i18n/locales/en.json index 8cbed8b27..a572adc85 100644 --- a/apps/browser-extension/src/i18n/locales/en.json +++ b/apps/browser-extension/src/i18n/locales/en.json @@ -190,7 +190,7 @@ "title": "Credentials", "addCredential": "Add Credential", "editCredential": "Edit Credential", - "deleteCredential": "Delete Credential", + "deleteCredential": "Delete Item", "credentialDetails": "Credential Details", "serviceName": "Service Name", "itemName": "Name", @@ -208,9 +208,8 @@ "createdAt": "Created", "updatedAt": "Last updated", "saveCredential": "Save credential", - "deleteCredentialTitle": "Delete Credential", - "deleteCredentialConfirm": "Are you sure you want to delete this credential? This action cannot be undone.", - "deleteCredentialConfirmation": "Are you sure you want to delete this item? This action cannot be undone.", + "deleteItemTitle": "Delete Item", + "deleteItemConfirm": "Are you sure you want to delete this item? This action cannot be undone.", "filters": { "all": "(All) Credentials", "passkeys": "Passkeys",