From ca75df8bb42f1e992bbd13d3bdc5f2e2190526c2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Sep 2025 12:24:13 -0400 Subject: [PATCH] Fixed issue with bunker. Made the modal more beautiful. --- lite/nostr-lite.js | 241 ++++++++++++++++++++++++++++++++------------- lite/ui/modal.js | 237 +++++++++++++++++++++++++++++++------------- 2 files changed, 342 insertions(+), 136 deletions(-) diff --git a/lite/nostr-lite.js b/lite/nostr-lite.js index 74ac713..66c9204 100644 --- a/lite/nostr-lite.js +++ b/lite/nostr-lite.js @@ -8,7 +8,7 @@ * Two-file architecture: * 1. Load nostr.bundle.js (official nostr-tools bundle) * 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes) - * Generated on: 2025-09-16T22:12:00.192Z + * Generated on: 2025-09-19T16:21:34.133Z */ // Verify dependencies are loaded @@ -44,7 +44,7 @@ const THEME_CSS = { --nl-primary-color: #000000; --nl-secondary-color: #ffffff; --nl-accent-color: #ff0000; - --nl-muted-color: #666666; + --nl-muted-color: #CCCCCC; --nl-font-family: "Courier New", Courier, monospace; --nl-border-radius: 15px; --nl-border-width: 3px; @@ -401,7 +401,7 @@ class Modal { closeButton.style.cssText = ` background: var(--nl-secondary-color); border: var(--nl-border-width) solid var(--nl-primary-color); - border-radius: var(--nl-border-radius); + border-radius: 4px; font-size: 28px; color: var(--nl-primary-color); cursor: pointer; @@ -585,21 +585,13 @@ class Modal { }; const iconDiv = document.createElement('div'); - // Replace emoji icons with text-based ones - const iconMap = { - '🔌': '[EXT]', - '🔑': '[KEY]', - '🌱': '[SEED]', - '🌐': '[NET]', - '👁️': '[VIEW]', - '📱': '[SMS]' - }; - iconDiv.textContent = iconMap[option.icon] || option.icon; + // Remove the icon entirely - no emojis or text-based icons + iconDiv.textContent = ''; iconDiv.style.cssText = ` font-size: 16px; font-weight: bold; margin-right: 16px; - width: 50px; + width: 0px; text-align: center; color: var(--nl-primary-color); font-family: var(--nl-font-family, 'Courier New', monospace); @@ -1494,10 +1486,6 @@ class Modal { _showConnectScreen() { this.modalBody.innerHTML = ''; - const title = document.createElement('h3'); - title.textContent = 'Connect to NIP-46 Remote Signer'; - title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; - const description = document.createElement('p'); description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.'; description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; @@ -1522,12 +1510,67 @@ class Modal { box-sizing: border-box; `; - // Users will enter the complete bunker connection string with relay info + // Add real-time bunker key validation + const formatHint = document.createElement('div'); + formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;'; const connectButton = document.createElement('button'); connectButton.textContent = 'Connect to Bunker'; - connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value); - connectButton.style.cssText = this._getButtonStyle(); + connectButton.disabled = true; + connectButton.onclick = () => { + if (!connectButton.disabled) { + this._handleNip46Connect(pubkeyInput.value); + } + }; + + // Set initial disabled state + connectButton.style.cssText = ` + display: block; + width: 100%; + padding: 12px; + border: var(--nl-border-width) solid var(--nl-muted-color); + border-radius: var(--nl-border-radius); + font-size: 16px; + font-weight: 500; + cursor: not-allowed; + transition: all 0.2s; + font-family: var(--nl-font-family, 'Courier New', monospace); + background: var(--nl-secondary-color); + color: var(--nl-muted-color); + margin-bottom: 12px; + `; + + pubkeyInput.oninput = () => { + const value = pubkeyInput.value.trim(); + if (!value) { + formatHint.textContent = ''; + // Disable button + connectButton.disabled = true; + connectButton.style.borderColor = 'var(--nl-muted-color)'; + connectButton.style.color = 'var(--nl-muted-color)'; + connectButton.style.cursor = 'not-allowed'; + return; + } + + const isValid = this._validateBunkerKey(value); + if (isValid) { + formatHint.textContent = '✅ Valid bunker connection format detected'; + formatHint.style.color = '#059669'; + // Enable button + connectButton.disabled = false; + connectButton.style.borderColor = 'var(--nl-primary-color)'; + connectButton.style.color = 'var(--nl-primary-color)'; + connectButton.style.cursor = 'pointer'; + } else { + formatHint.textContent = '❌ Invalid format - must be bunker://, npub, or 64-char hex'; + formatHint.style.color = '#dc2626'; + // Disable button + connectButton.disabled = true; + connectButton.style.borderColor = 'var(--nl-muted-color)'; + connectButton.style.color = 'var(--nl-muted-color)'; + connectButton.style.cursor = 'not-allowed'; + } + }; const backButton = document.createElement('button'); backButton.textContent = 'Back'; @@ -1536,14 +1579,49 @@ class Modal { formGroup.appendChild(label); formGroup.appendChild(pubkeyInput); + formGroup.appendChild(formatHint); - this.modalBody.appendChild(title); this.modalBody.appendChild(description); this.modalBody.appendChild(formGroup); this.modalBody.appendChild(connectButton); this.modalBody.appendChild(backButton); } + _validateBunkerKey(bunkerKey) { + try { + const trimmed = bunkerKey.trim(); + + // Check for bunker:// format + if (trimmed.startsWith('bunker://')) { + // Should have format: bunker://pubkey or bunker://pubkey?param=value + const match = trimmed.match(/^bunker:\/\/([0-9a-fA-F]{64})(\?.*)?$/); + return !!match; + } + + // Check for npub format + if (trimmed.startsWith('npub1') && trimmed.length === 63) { + try { + if (window.NostrTools?.nip19) { + const decoded = window.NostrTools.nip19.decode(trimmed); + return decoded.type === 'npub'; + } + } catch { + return false; + } + } + + // Check for hex format (64 characters, valid hex) + if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) { + return true; + } + + return false; + } catch (error) { + console.log('Bunker key validation failed:', error.message); + return false; + } + } + _handleNip46Connect(bunkerPubkey) { if (!bunkerPubkey || !bunkerPubkey.length) { this._showError('Bunker pubkey is required'); @@ -1613,9 +1691,9 @@ class Modal { const localSecretKey = window.NostrTools.generateSecretKey(); console.log('Generated local client keypair for NIP-46 session'); - // Use nostr-tools BunkerSigner constructor + // Use nostr-tools BunkerSigner factory method (not constructor - it's private) console.log('Creating nip46 BunkerSigner...'); - const signer = new window.NostrTools.nip46.BunkerSigner(localSecretKey, bunkerPointer, { + const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, { onauth: (url) => { console.log('Received auth URL from bunker:', url); // Open auth URL in popup or redirect @@ -1695,12 +1773,8 @@ class Modal { _showSeedPhraseScreen() { this.modalBody.innerHTML = ''; - const title = document.createElement('h3'); - title.textContent = 'Import from Seed Phrase'; - title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; - const description = document.createElement('p'); - description.textContent = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts:'; + description.innerHTML = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts, or generate new.'; description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;'; const textarea = document.createElement('textarea'); @@ -1723,10 +1797,40 @@ class Modal { const formatHint = document.createElement('div'); formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;'; + const importButton = document.createElement('button'); + importButton.textContent = 'Import Accounts'; + importButton.disabled = true; + importButton.onclick = () => { + if (!importButton.disabled) { + this._importFromSeedPhrase(textarea.value); + } + }; + + // Set initial disabled state + importButton.style.cssText = ` + display: block; + width: 100%; + padding: 12px; + border: var(--nl-border-width) solid var(--nl-muted-color); + border-radius: var(--nl-border-radius); + font-size: 16px; + font-weight: 500; + cursor: not-allowed; + transition: all 0.2s; + font-family: var(--nl-font-family, 'Courier New', monospace); + background: var(--nl-secondary-color); + color: var(--nl-muted-color); + `; + textarea.oninput = () => { const value = textarea.value.trim(); if (!value) { formatHint.textContent = ''; + // Disable button + importButton.disabled = true; + importButton.style.borderColor = 'var(--nl-muted-color)'; + importButton.style.color = 'var(--nl-muted-color)'; + importButton.style.cursor = 'not-allowed'; return; } @@ -1735,35 +1839,46 @@ class Modal { const wordCount = value.split(/\s+/).length; formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`; formatHint.style.color = '#059669'; + // Enable button + importButton.disabled = false; + importButton.style.borderColor = 'var(--nl-primary-color)'; + importButton.style.color = 'var(--nl-primary-color)'; + importButton.style.cursor = 'pointer'; } else { formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words'; formatHint.style.color = '#dc2626'; + // Disable button + importButton.disabled = true; + importButton.style.borderColor = 'var(--nl-muted-color)'; + importButton.style.color = 'var(--nl-muted-color)'; + importButton.style.cursor = 'not-allowed'; } }; - // Generate new seed phrase button - const generateButton = document.createElement('button'); - generateButton.textContent = 'Generate New Seed Phrase'; - generateButton.onclick = () => this._generateNewSeedPhrase(textarea, formatHint); - generateButton.style.cssText = this._getButtonStyle() + 'margin-bottom: 12px;'; - - const importButton = document.createElement('button'); - importButton.textContent = 'Import Accounts'; - importButton.onclick = () => this._importFromSeedPhrase(textarea.value); - importButton.style.cssText = this._getButtonStyle(); - const backButton = document.createElement('button'); backButton.textContent = 'Back'; backButton.onclick = () => this._renderLoginOptions(); backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; - this.modalBody.appendChild(title); this.modalBody.appendChild(description); this.modalBody.appendChild(textarea); this.modalBody.appendChild(formatHint); - this.modalBody.appendChild(generateButton); this.modalBody.appendChild(importButton); this.modalBody.appendChild(backButton); + + // Add click handler for the "generate new" link + const generateLink = document.getElementById('generate-new'); + if (generateLink) { + generateLink.addEventListener('mouseenter', () => { + generateLink.style.color = 'var(--nl-accent-color)'; + }); + generateLink.addEventListener('mouseleave', () => { + generateLink.style.color = 'var(--nl-primary-color)'; + }); + generateLink.addEventListener('click', () => { + this._generateNewSeedPhrase(textarea, formatHint); + }); + } } _generateNewSeedPhrase(textarea, formatHint) { @@ -1779,12 +1894,12 @@ class Modal { // Set the generated mnemonic in the textarea textarea.value = mnemonic; - // Trigger validation to show it's valid - const wordCount = mnemonic.split(/\s+/).length; - formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`; - formatHint.style.color = '#059669'; + // Trigger the oninput event to properly validate and enable the button + if (textarea.oninput) { + textarea.oninput(); + } - console.log('Generated new seed phrase:', wordCount, 'words'); + console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words'); } catch (error) { console.error('Failed to generate seed phrase:', error); @@ -1866,15 +1981,10 @@ class Modal { _showAccountSelection(accounts) { this.modalBody.innerHTML = ''; - const title = document.createElement('h3'); - title.textContent = 'Select Account'; - title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; - const description = document.createElement('p'); description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`; description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; - this.modalBody.appendChild(title); this.modalBody.appendChild(description); // Create table for account selection @@ -1892,8 +2002,7 @@ class Modal { thead.innerHTML = ` # - Public Key (npub) - Action + Use `; table.appendChild(thead); @@ -1908,31 +2017,26 @@ class Modal { indexCell.textContent = account.index; indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;'; - const pubkeyCell = document.createElement('td'); - pubkeyCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db; font-family: monospace; word-break: break-all;'; - - // Show truncated npub for readability - const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`; - pubkeyCell.innerHTML = ` - ${truncatedNpub}
- Full: ${account.npub} - `; - const actionCell = document.createElement('td'); - actionCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db;'; + actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;'; + + // Show truncated npub in the button + const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`; const selectButton = document.createElement('button'); - selectButton.textContent = 'Use'; + selectButton.textContent = truncatedNpub; selectButton.onclick = () => this._selectAccount(account); selectButton.style.cssText = ` - padding: 4px 12px; + width: 100%; + padding: 8px 12px; font-size: 11px; background: var(--nl-secondary-color); color: var(--nl-primary-color); border: 1px solid var(--nl-primary-color); border-radius: 4px; cursor: pointer; - font-family: var(--nl-font-family, 'Courier New', monospace); + font-family: 'Courier New', monospace; + text-align: center; `; selectButton.onmouseover = () => { selectButton.style.borderColor = 'var(--nl-accent-color)'; @@ -1944,7 +2048,6 @@ class Modal { actionCell.appendChild(selectButton); row.appendChild(indexCell); - row.appendChild(pubkeyCell); row.appendChild(actionCell); tbody.appendChild(row); }); diff --git a/lite/ui/modal.js b/lite/ui/modal.js index da5acac..a790c07 100644 --- a/lite/ui/modal.js +++ b/lite/ui/modal.js @@ -104,7 +104,7 @@ class Modal { closeButton.style.cssText = ` background: var(--nl-secondary-color); border: var(--nl-border-width) solid var(--nl-primary-color); - border-radius: var(--nl-border-radius); + border-radius: 4px; font-size: 28px; color: var(--nl-primary-color); cursor: pointer; @@ -288,21 +288,13 @@ class Modal { }; const iconDiv = document.createElement('div'); - // Replace emoji icons with text-based ones - const iconMap = { - '🔌': '[EXT]', - '🔑': '[KEY]', - '🌱': '[SEED]', - '🌐': '[NET]', - '👁️': '[VIEW]', - '📱': '[SMS]' - }; - iconDiv.textContent = iconMap[option.icon] || option.icon; + // Remove the icon entirely - no emojis or text-based icons + iconDiv.textContent = ''; iconDiv.style.cssText = ` font-size: 16px; font-weight: bold; margin-right: 16px; - width: 50px; + width: 0px; text-align: center; color: var(--nl-primary-color); font-family: var(--nl-font-family, 'Courier New', monospace); @@ -1197,10 +1189,6 @@ class Modal { _showConnectScreen() { this.modalBody.innerHTML = ''; - const title = document.createElement('h3'); - title.textContent = 'Connect to NIP-46 Remote Signer'; - title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; - const description = document.createElement('p'); description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.'; description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; @@ -1225,12 +1213,67 @@ class Modal { box-sizing: border-box; `; - // Users will enter the complete bunker connection string with relay info + // Add real-time bunker key validation + const formatHint = document.createElement('div'); + formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;'; const connectButton = document.createElement('button'); connectButton.textContent = 'Connect to Bunker'; - connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value); - connectButton.style.cssText = this._getButtonStyle(); + connectButton.disabled = true; + connectButton.onclick = () => { + if (!connectButton.disabled) { + this._handleNip46Connect(pubkeyInput.value); + } + }; + + // Set initial disabled state + connectButton.style.cssText = ` + display: block; + width: 100%; + padding: 12px; + border: var(--nl-border-width) solid var(--nl-muted-color); + border-radius: var(--nl-border-radius); + font-size: 16px; + font-weight: 500; + cursor: not-allowed; + transition: all 0.2s; + font-family: var(--nl-font-family, 'Courier New', monospace); + background: var(--nl-secondary-color); + color: var(--nl-muted-color); + margin-bottom: 12px; + `; + + pubkeyInput.oninput = () => { + const value = pubkeyInput.value.trim(); + if (!value) { + formatHint.textContent = ''; + // Disable button + connectButton.disabled = true; + connectButton.style.borderColor = 'var(--nl-muted-color)'; + connectButton.style.color = 'var(--nl-muted-color)'; + connectButton.style.cursor = 'not-allowed'; + return; + } + + const isValid = this._validateBunkerKey(value); + if (isValid) { + formatHint.textContent = '✅ Valid bunker connection format detected'; + formatHint.style.color = '#059669'; + // Enable button + connectButton.disabled = false; + connectButton.style.borderColor = 'var(--nl-primary-color)'; + connectButton.style.color = 'var(--nl-primary-color)'; + connectButton.style.cursor = 'pointer'; + } else { + formatHint.textContent = '❌ Invalid format - must be bunker://, npub, or 64-char hex'; + formatHint.style.color = '#dc2626'; + // Disable button + connectButton.disabled = true; + connectButton.style.borderColor = 'var(--nl-muted-color)'; + connectButton.style.color = 'var(--nl-muted-color)'; + connectButton.style.cursor = 'not-allowed'; + } + }; const backButton = document.createElement('button'); backButton.textContent = 'Back'; @@ -1239,14 +1282,49 @@ class Modal { formGroup.appendChild(label); formGroup.appendChild(pubkeyInput); + formGroup.appendChild(formatHint); - this.modalBody.appendChild(title); this.modalBody.appendChild(description); this.modalBody.appendChild(formGroup); this.modalBody.appendChild(connectButton); this.modalBody.appendChild(backButton); } + _validateBunkerKey(bunkerKey) { + try { + const trimmed = bunkerKey.trim(); + + // Check for bunker:// format + if (trimmed.startsWith('bunker://')) { + // Should have format: bunker://pubkey or bunker://pubkey?param=value + const match = trimmed.match(/^bunker:\/\/([0-9a-fA-F]{64})(\?.*)?$/); + return !!match; + } + + // Check for npub format + if (trimmed.startsWith('npub1') && trimmed.length === 63) { + try { + if (window.NostrTools?.nip19) { + const decoded = window.NostrTools.nip19.decode(trimmed); + return decoded.type === 'npub'; + } + } catch { + return false; + } + } + + // Check for hex format (64 characters, valid hex) + if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) { + return true; + } + + return false; + } catch (error) { + console.log('Bunker key validation failed:', error.message); + return false; + } + } + _handleNip46Connect(bunkerPubkey) { if (!bunkerPubkey || !bunkerPubkey.length) { this._showError('Bunker pubkey is required'); @@ -1316,9 +1394,9 @@ class Modal { const localSecretKey = window.NostrTools.generateSecretKey(); console.log('Generated local client keypair for NIP-46 session'); - // Use nostr-tools BunkerSigner constructor + // Use nostr-tools BunkerSigner factory method (not constructor - it's private) console.log('Creating nip46 BunkerSigner...'); - const signer = new window.NostrTools.nip46.BunkerSigner(localSecretKey, bunkerPointer, { + const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, { onauth: (url) => { console.log('Received auth URL from bunker:', url); // Open auth URL in popup or redirect @@ -1398,12 +1476,8 @@ class Modal { _showSeedPhraseScreen() { this.modalBody.innerHTML = ''; - const title = document.createElement('h3'); - title.textContent = 'Import from Seed Phrase'; - title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; - const description = document.createElement('p'); - description.textContent = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts:'; + description.innerHTML = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts, or generate new.'; description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;'; const textarea = document.createElement('textarea'); @@ -1426,10 +1500,40 @@ class Modal { const formatHint = document.createElement('div'); formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;'; + const importButton = document.createElement('button'); + importButton.textContent = 'Import Accounts'; + importButton.disabled = true; + importButton.onclick = () => { + if (!importButton.disabled) { + this._importFromSeedPhrase(textarea.value); + } + }; + + // Set initial disabled state + importButton.style.cssText = ` + display: block; + width: 100%; + padding: 12px; + border: var(--nl-border-width) solid var(--nl-muted-color); + border-radius: var(--nl-border-radius); + font-size: 16px; + font-weight: 500; + cursor: not-allowed; + transition: all 0.2s; + font-family: var(--nl-font-family, 'Courier New', monospace); + background: var(--nl-secondary-color); + color: var(--nl-muted-color); + `; + textarea.oninput = () => { const value = textarea.value.trim(); if (!value) { formatHint.textContent = ''; + // Disable button + importButton.disabled = true; + importButton.style.borderColor = 'var(--nl-muted-color)'; + importButton.style.color = 'var(--nl-muted-color)'; + importButton.style.cursor = 'not-allowed'; return; } @@ -1438,35 +1542,46 @@ class Modal { const wordCount = value.split(/\s+/).length; formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`; formatHint.style.color = '#059669'; + // Enable button + importButton.disabled = false; + importButton.style.borderColor = 'var(--nl-primary-color)'; + importButton.style.color = 'var(--nl-primary-color)'; + importButton.style.cursor = 'pointer'; } else { formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words'; formatHint.style.color = '#dc2626'; + // Disable button + importButton.disabled = true; + importButton.style.borderColor = 'var(--nl-muted-color)'; + importButton.style.color = 'var(--nl-muted-color)'; + importButton.style.cursor = 'not-allowed'; } }; - // Generate new seed phrase button - const generateButton = document.createElement('button'); - generateButton.textContent = 'Generate New Seed Phrase'; - generateButton.onclick = () => this._generateNewSeedPhrase(textarea, formatHint); - generateButton.style.cssText = this._getButtonStyle() + 'margin-bottom: 12px;'; - - const importButton = document.createElement('button'); - importButton.textContent = 'Import Accounts'; - importButton.onclick = () => this._importFromSeedPhrase(textarea.value); - importButton.style.cssText = this._getButtonStyle(); - const backButton = document.createElement('button'); backButton.textContent = 'Back'; backButton.onclick = () => this._renderLoginOptions(); backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;'; - this.modalBody.appendChild(title); this.modalBody.appendChild(description); this.modalBody.appendChild(textarea); this.modalBody.appendChild(formatHint); - this.modalBody.appendChild(generateButton); this.modalBody.appendChild(importButton); this.modalBody.appendChild(backButton); + + // Add click handler for the "generate new" link + const generateLink = document.getElementById('generate-new'); + if (generateLink) { + generateLink.addEventListener('mouseenter', () => { + generateLink.style.color = 'var(--nl-accent-color)'; + }); + generateLink.addEventListener('mouseleave', () => { + generateLink.style.color = 'var(--nl-primary-color)'; + }); + generateLink.addEventListener('click', () => { + this._generateNewSeedPhrase(textarea, formatHint); + }); + } } _generateNewSeedPhrase(textarea, formatHint) { @@ -1482,12 +1597,12 @@ class Modal { // Set the generated mnemonic in the textarea textarea.value = mnemonic; - // Trigger validation to show it's valid - const wordCount = mnemonic.split(/\s+/).length; - formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`; - formatHint.style.color = '#059669'; + // Trigger the oninput event to properly validate and enable the button + if (textarea.oninput) { + textarea.oninput(); + } - console.log('Generated new seed phrase:', wordCount, 'words'); + console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words'); } catch (error) { console.error('Failed to generate seed phrase:', error); @@ -1569,15 +1684,10 @@ class Modal { _showAccountSelection(accounts) { this.modalBody.innerHTML = ''; - const title = document.createElement('h3'); - title.textContent = 'Select Account'; - title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; - const description = document.createElement('p'); description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`; description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;'; - this.modalBody.appendChild(title); this.modalBody.appendChild(description); // Create table for account selection @@ -1595,8 +1705,7 @@ class Modal { thead.innerHTML = ` # - Public Key (npub) - Action + Use `; table.appendChild(thead); @@ -1611,31 +1720,26 @@ class Modal { indexCell.textContent = account.index; indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;'; - const pubkeyCell = document.createElement('td'); - pubkeyCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db; font-family: monospace; word-break: break-all;'; - - // Show truncated npub for readability - const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`; - pubkeyCell.innerHTML = ` - ${truncatedNpub}
- Full: ${account.npub} - `; - const actionCell = document.createElement('td'); - actionCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db;'; + actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;'; + + // Show truncated npub in the button + const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`; const selectButton = document.createElement('button'); - selectButton.textContent = 'Use'; + selectButton.textContent = truncatedNpub; selectButton.onclick = () => this._selectAccount(account); selectButton.style.cssText = ` - padding: 4px 12px; + width: 100%; + padding: 8px 12px; font-size: 11px; background: var(--nl-secondary-color); color: var(--nl-primary-color); border: 1px solid var(--nl-primary-color); border-radius: 4px; cursor: pointer; - font-family: var(--nl-font-family, 'Courier New', monospace); + font-family: 'Courier New', monospace; + text-align: center; `; selectButton.onmouseover = () => { selectButton.style.borderColor = 'var(--nl-accent-color)'; @@ -1647,7 +1751,6 @@ class Modal { actionCell.appendChild(selectButton); row.appendChild(indexCell); - row.appendChild(pubkeyCell); row.appendChild(actionCell); tbody.appendChild(row); });