fix: derive imported profile name
This commit is contained in:
parent
470a98f16e
commit
b368958a91
2 changed files with 76 additions and 0 deletions
43
frontend/src/screens/ImportProfileModal.tsx
Normal file
43
frontend/src/screens/ImportProfileModal.tsx
Normal 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>;
|
||||||
|
}
|
||||||
|
|
@ -136,6 +136,16 @@ pub fn import_profile(
|
||||||
};
|
};
|
||||||
let created_at = unix_timestamp()?;
|
let created_at = unix_timestamp()?;
|
||||||
let metadata = fetch_profile_metadata(&public_key, &relays::enabled_urls(settings));
|
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();
|
let is_active = vault.active_profile.is_none();
|
||||||
if is_active {
|
if is_active {
|
||||||
vault.active_profile = Some(public_key.clone());
|
vault.active_profile = Some(public_key.clone());
|
||||||
|
|
@ -520,6 +530,13 @@ fn fetch_profile_metadata(npub: &str, relay_urls: &[String]) -> Option<Metadata>
|
||||||
.flatten()
|
.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(
|
async fn publish_metadata_async(
|
||||||
keys: &Keys,
|
keys: &Keys,
|
||||||
label: &str,
|
label: &str,
|
||||||
|
|
@ -786,6 +803,22 @@ mod tests {
|
||||||
assert_eq!(vault.active_profile.as_deref(), Some("npub1alice"));
|
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]
|
#[test]
|
||||||
fn summaries_never_include_secret_keys() {
|
fn summaries_never_include_secret_keys() {
|
||||||
let vault = populated_vault();
|
let vault = populated_vault();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue