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);
});