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;
|
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 */
|
/* Responsive Design for Mode Selection */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.mode-buttons {
|
.mode-buttons {
|
||||||
|
|||||||
@ -16,6 +16,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="mainPairingButton" class="primary-btn">Authenticate with Browser</button>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
|
|||||||
@ -120,6 +120,9 @@ export async function initHomePage(): Promise<void> {
|
|||||||
|
|
||||||
// Set up main pairing interface
|
// Set up main pairing interface
|
||||||
setupMainPairing();
|
setupMainPairing();
|
||||||
|
|
||||||
|
// Set up account actions
|
||||||
|
setupAccountActions();
|
||||||
|
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
container.querySelectorAll('.tab').forEach(tab => {
|
container.querySelectorAll('.tab').forEach(tab => {
|
||||||
@ -520,10 +523,10 @@ export function setupIframePairingButtons() {
|
|||||||
// Main Pairing Interface
|
// Main Pairing Interface
|
||||||
export function setupMainPairing(): void {
|
export function setupMainPairing(): void {
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
|
|
||||||
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
|
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
|
||||||
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||||
|
|
||||||
if (mainPairingButton) {
|
if (mainPairingButton) {
|
||||||
mainPairingButton.addEventListener('click', async () => {
|
mainPairingButton.addEventListener('click', async () => {
|
||||||
await handleMainPairing();
|
await handleMainPairing();
|
||||||
@ -535,7 +538,7 @@ async function handleMainPairing(): Promise<void> {
|
|||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||||
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
|
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update UI
|
// Update UI
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
@ -550,16 +553,16 @@ async function handleMainPairing(): Promise<void> {
|
|||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
const hasCredentials = await secureCredentialsService.hasCredentials();
|
const hasCredentials = await secureCredentialsService.hasCredentials();
|
||||||
|
|
||||||
if (hasCredentials) {
|
if (hasCredentials) {
|
||||||
// Existing pairing - decrypt credentials
|
// Existing pairing - decrypt credentials
|
||||||
console.log('🔓 Existing credentials found, decrypting...');
|
console.log('🔓 Existing credentials found, decrypting...');
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
|
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
await secureCredentialsService.retrieveCredentials(''); // Empty password for WebAuthn
|
await secureCredentialsService.retrieveCredentials(''); // Empty password for WebAuthn
|
||||||
|
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
|
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
|
||||||
}
|
}
|
||||||
@ -569,31 +572,130 @@ async function handleMainPairing(): Promise<void> {
|
|||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new secure pairing...</span>';
|
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new secure pairing...</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will trigger the WebAuthn flow and create new credentials
|
// This will trigger the WebAuthn flow and create new credentials
|
||||||
await prepareAndSendPairingTx();
|
await prepareAndSendPairingTx();
|
||||||
|
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New pairing created successfully</span>';
|
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New pairing created successfully</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-enable button
|
// Re-enable button
|
||||||
if (mainPairingButton) {
|
if (mainPairingButton) {
|
||||||
mainPairingButton.disabled = false;
|
mainPairingButton.disabled = false;
|
||||||
mainPairingButton.textContent = 'Authenticate with Browser';
|
mainPairingButton.textContent = 'Authenticate with Browser';
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Pairing failed:', error);
|
console.error('Pairing failed:', error);
|
||||||
|
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Authentication failed</span>';
|
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Authentication failed</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainPairingButton) {
|
if (mainPairingButton) {
|
||||||
mainPairingButton.disabled = false;
|
mainPairingButton.disabled = false;
|
||||||
mainPairingButton.textContent = 'Authenticate with Browser';
|
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