fix: derive imported profile name

This commit is contained in:
Avi 2026-09-01 09:48:38 -05:00
commit b368958a91
2 changed files with 76 additions and 0 deletions

View file

@ -0,0 +1,43 @@
import { useEffect, useRef, useState, type FormEvent } from 'react';
import { Button } from '../components/Button';
import { ErrorText } from '../components/ErrorText';
import { Modal } from '../components/Modal';
import { useApp } from '../state/AppProvider';
export function ImportProfileModal({ open, onClose }: { open: boolean; onClose: () => void }) {
const { importProfile } = useApp();
const [secret, setSecret] = useState('');
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const labelRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (open) {
setSecret(''); setError(null); setSaving(false);
requestAnimationFrame(() => labelRef.current?.focus());
}
}, [open]);
const submit = async (event: FormEvent) => {
event.preventDefault();
if (!secret.trim() || saving) return;
setSaving(true); setError(null);
try { await importProfile('', secret.trim()); onClose(); }
catch (err) { setError(err instanceof Error ? err.message : String(err)); setSaving(false); }
};
return <Modal open={open} title="Add an existing account" onClose={onClose}>
<form onSubmit={submit} noValidate>
<p className="muted">Import an account using its private key. The key stays in your local vault and is never displayed.</p>
<div className="field">
<label htmlFor="import-profile-secret">Private key</label>
<input id="import-profile-secret" type="password" value={secret} onChange={(e) => setSecret(e.target.value)} placeholder="nsec1... or 64-character hex" autoComplete="off" />
{error && <ErrorText>{error}</ErrorText>}
</div>
<div className="modal-actions">
<Button variant="ghost" onClick={onClose} disabled={saving}>Cancel</Button>
<Button variant="primary" type="submit" loading={saving} disabled={!secret.trim()}>{saving ? 'Adding…' : 'Add account'}</Button>
</div>
</form>
</Modal>;
}

View file

@ -136,6 +136,16 @@ pub fn import_profile(
};
let created_at = unix_timestamp()?;
let metadata = fetch_profile_metadata(&public_key, &relays::enabled_urls(settings));
let label = if label.trim().is_empty() {
metadata
.as_ref()
.and_then(|m| m.display_name.as_deref().or(m.name.as_deref()))
.filter(|name| !name.trim().is_empty())
.map(str::to_string)
.unwrap_or_else(|| shorten_npub(&keys.public_key()))
} else {
label
};
let is_active = vault.active_profile.is_none();
if is_active {
vault.active_profile = Some(public_key.clone());
@ -520,6 +530,13 @@ fn fetch_profile_metadata(npub: &str, relay_urls: &[String]) -> Option<Metadata>
.flatten()
}
fn shorten_npub(public_key: &PublicKey) -> String {
public_key
.to_bech32()
.map(|npub| format!("{}…{}", &npub[..10], &npub[npub.len() - 6..]))
.unwrap_or_else(|_| "Imported account".to_string())
}
async fn publish_metadata_async(
keys: &Keys,
label: &str,
@ -786,6 +803,22 @@ mod tests {
assert_eq!(vault.active_profile.as_deref(), Some("npub1alice"));
}
#[test]
fn import_profile_allows_empty_label() {
let mut vault = Vault::empty();
let keys = Keys::generate();
let summary = import_profile(
&mut vault,
String::new(),
&keys.secret_key().to_bech32().unwrap(),
None,
&offline_settings(),
)
.unwrap();
assert!(!summary.label.is_empty());
assert_eq!(summary.npub, keys.public_key().to_bech32().unwrap());
}
#[test]
fn summaries_never_include_secret_keys() {
let vault = populated_vault();