feat: Improve UI layout and add account deletion
**Motivations :** - Make status field same width as authentication button - Add secure account deletion functionality - Improve visual consistency of interface **Modifications :** - Adjusted status container width to match button width - Added red delete account button at bottom - Implemented secure account deletion with double confirmation - Added comprehensive data cleanup (credentials, storage, IndexedDB, caches) - Enhanced CSS styling for status indicator and danger button **Pages affectées :** - src/pages/home/home.html - Added delete account button - src/pages/home/home.ts - Added account deletion logic - src/4nk.css - Enhanced styling for status and danger button
This commit is contained in:
parent
802a77b568
commit
33935f4b18
49
src/4nk.css
49
src/4nk.css
@ -1181,6 +1181,55 @@ select[data-multi-select-plugin] {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* Status Container - Match button width */
|
||||
.status-container {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
/* Account Actions */
|
||||
.account-actions {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e9ecef;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.danger-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.danger-btn:hover {
|
||||
background: #c82333;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
|
||||
}
|
||||
|
||||
.danger-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
/* Responsive Design for Mode Selection */
|
||||
@media (max-width: 768px) {
|
||||
.mode-buttons {
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
</div>
|
||||
|
||||
<button id="mainPairingButton" class="primary-btn">Authenticate with Browser</button>
|
||||
|
||||
<div class="account-actions">
|
||||
<button id="deleteAccountButton" class="danger-btn">🗑️ Delete Account</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
|
||||
@ -120,6 +120,9 @@ export async function initHomePage(): Promise<void> {
|
||||
|
||||
// Set up main pairing interface
|
||||
setupMainPairing();
|
||||
|
||||
// Set up account actions
|
||||
setupAccountActions();
|
||||
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||
container.querySelectorAll('.tab').forEach(tab => {
|
||||
@ -520,10 +523,10 @@ export function setupIframePairingButtons() {
|
||||
// Main Pairing Interface
|
||||
export function setupMainPairing(): void {
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||
|
||||
|
||||
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
|
||||
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||
|
||||
|
||||
if (mainPairingButton) {
|
||||
mainPairingButton.addEventListener('click', async () => {
|
||||
await handleMainPairing();
|
||||
@ -535,7 +538,7 @@ async function handleMainPairing(): Promise<void> {
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
|
||||
|
||||
|
||||
try {
|
||||
// Update UI
|
||||
if (mainStatus) {
|
||||
@ -550,16 +553,16 @@ async function handleMainPairing(): Promise<void> {
|
||||
const service = await Services.getInstance();
|
||||
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||
const hasCredentials = await secureCredentialsService.hasCredentials();
|
||||
|
||||
|
||||
if (hasCredentials) {
|
||||
// Existing pairing - decrypt credentials
|
||||
console.log('🔓 Existing credentials found, decrypting...');
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
|
||||
}
|
||||
|
||||
|
||||
await secureCredentialsService.retrieveCredentials(''); // Empty password for WebAuthn
|
||||
|
||||
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
|
||||
}
|
||||
@ -569,31 +572,130 @@ async function handleMainPairing(): Promise<void> {
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new secure pairing...</span>';
|
||||
}
|
||||
|
||||
|
||||
// This will trigger the WebAuthn flow and create new credentials
|
||||
await prepareAndSendPairingTx();
|
||||
|
||||
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New pairing created successfully</span>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Re-enable button
|
||||
if (mainPairingButton) {
|
||||
mainPairingButton.disabled = false;
|
||||
mainPairingButton.textContent = 'Authenticate with Browser';
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Pairing failed:', error);
|
||||
|
||||
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Authentication failed</span>';
|
||||
}
|
||||
|
||||
|
||||
if (mainPairingButton) {
|
||||
mainPairingButton.disabled = false;
|
||||
mainPairingButton.textContent = 'Authenticate with Browser';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Account Actions
|
||||
export function setupAccountActions(): void {
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||
|
||||
const deleteAccountButton = container.querySelector('#deleteAccountButton') as HTMLButtonElement;
|
||||
|
||||
if (deleteAccountButton) {
|
||||
deleteAccountButton.addEventListener('click', async () => {
|
||||
await handleDeleteAccount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteAccount(): Promise<void> {
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||
|
||||
// Confirmation dialog
|
||||
const confirmed = confirm(
|
||||
'⚠️ WARNING: This will permanently delete your account and all associated data.\n\n' +
|
||||
'This action cannot be undone!\n\n' +
|
||||
'Are you sure you want to delete your account?'
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Double confirmation
|
||||
const doubleConfirmed = confirm(
|
||||
'🚨 FINAL WARNING: You are about to permanently delete your account.\n\n' +
|
||||
'All your data, credentials, and pairings will be lost forever.\n\n' +
|
||||
'Type "DELETE" to confirm (case sensitive):'
|
||||
);
|
||||
|
||||
if (doubleConfirmed) {
|
||||
const userInput = prompt('Type "DELETE" to confirm account deletion:');
|
||||
if (userInput !== 'DELETE') {
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<span style="color: var(--warning-color)">❌ Account deletion cancelled - confirmation text did not match</span>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Deleting account and all data...</span>';
|
||||
}
|
||||
|
||||
// Get services
|
||||
const service = await Services.getInstance();
|
||||
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||
|
||||
// Delete all credentials
|
||||
await secureCredentialsService.deleteCredentials();
|
||||
|
||||
// Clear all local storage
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
// Clear IndexedDB
|
||||
if ('indexedDB' in window) {
|
||||
const databases = await indexedDB.databases();
|
||||
for (const db of databases) {
|
||||
if (db.name) {
|
||||
indexedDB.deleteDatabase(db.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear service worker caches
|
||||
if ('caches' in window) {
|
||||
const cacheNames = await caches.keys();
|
||||
await Promise.all(
|
||||
cacheNames.map(cacheName => caches.delete(cacheName))
|
||||
);
|
||||
}
|
||||
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Account and all data deleted successfully</span>';
|
||||
}
|
||||
|
||||
// Reload the page to start fresh
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Account deletion failed:', error);
|
||||
|
||||
if (mainStatus) {
|
||||
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Failed to delete account</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user