feat(ui): drop the pairing label step — profile names come from the account
Sep 25 feedback: the prefilled 'Amber' name had to be cleared letter by
letter before the QR would appear (the input fight), and naming should
be automatic anyway. One click on 'Sign in with a signer app (Amber)'
now goes straight to the QR with the seed label 'Amber'; the existing
adopt_identity background enrichment fetches the account's kind-0 after
the handshake and upgrades the profile row to the account's REAL
display name whenever it still carries our seed (a manual rename in
Profiles always wins and is never overwritten).
frontend: 125 tests green (pairing test asserts the one-click path and
the 'Amber' seed label), typecheck/lint/format/electron:build/build
green. Rust untouched — the auto-naming enrichment already shipped at
a76d8df.
This commit is contained in:
parent
e8d11701a3
commit
fc2fe93161
2 changed files with 13 additions and 62 deletions
|
|
@ -12,7 +12,7 @@ interface CreateProfileModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Phase = 'choice' | 'pair-label' | 'pairing' | 'paired' | 'local' | 'creating' | 'success';
|
type Phase = 'choice' | 'pairing' | 'paired' | 'local' | 'creating' | 'success';
|
||||||
|
|
||||||
export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) {
|
export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) {
|
||||||
const { state, createProfile, nip46PairStart, nip46Status, nip46Disconnect, refresh } = useApp();
|
const { state, createProfile, nip46PairStart, nip46Status, nip46Disconnect, refresh } = useApp();
|
||||||
|
|
@ -39,7 +39,7 @@ export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) {
|
||||||
|
|
||||||
// Let the local-form input take focus once that step mounts.
|
// Let the local-form input take focus once that step mounts.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (phase === 'local' || phase === 'pair-label') {
|
if (phase === 'local') {
|
||||||
const frame = requestAnimationFrame(() => inputRef.current?.focus());
|
const frame = requestAnimationFrame(() => inputRef.current?.focus());
|
||||||
return () => cancelAnimationFrame(frame);
|
return () => cancelAnimationFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +133,12 @@ export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) {
|
||||||
const startPairing = async () => {
|
const startPairing = async () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
|
// No user-typed label: the profile is named automatically. The
|
||||||
|
// backend fetches the account's kind-0 after the handshake and
|
||||||
|
// upgrades this seed label to the account's real display name
|
||||||
|
// (adopt_identity background enrichment); a nameless account keeps
|
||||||
|
// 'Amber', which beats a manual step the user must fight with a
|
||||||
|
// backspace key (Sep 25 feedback).
|
||||||
const status = await nip46PairStart(label.trim() || 'Amber');
|
const status = await nip46PairStart(label.trim() || 'Amber');
|
||||||
if (status.pairing_uri) setLivePairingUri(status.pairing_uri);
|
if (status.pairing_uri) setLivePairingUri(status.pairing_uri);
|
||||||
setPhase('pairing');
|
setPhase('pairing');
|
||||||
|
|
@ -170,13 +176,7 @@ export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) {
|
||||||
</div>
|
</div>
|
||||||
{error && <ErrorText id={errorId}>{error}</ErrorText>}
|
{error && <ErrorText id={errorId}>{error}</ErrorText>}
|
||||||
<div style={{ display: 'grid', gap: 12 }}>
|
<div style={{ display: 'grid', gap: 12 }}>
|
||||||
<Button
|
<Button variant="primary" onClick={() => void startPairing()}>
|
||||||
variant="primary"
|
|
||||||
onClick={() => {
|
|
||||||
if (!label.trim()) setLabel('Amber');
|
|
||||||
setPhase('pair-label');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon name="key" size={16} />
|
<Icon name="key" size={16} />
|
||||||
Sign in with a signer app (Amber)
|
Sign in with a signer app (Amber)
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -196,51 +196,6 @@ export function CreateProfileModal({ open, onClose }: CreateProfileModalProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phase === 'pair-label') {
|
|
||||||
return (
|
|
||||||
<Modal open={open} title="Sign in with a signer" onClose={handleClose}>
|
|
||||||
<form
|
|
||||||
onSubmit={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
void startPairing();
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
>
|
|
||||||
<div className="create-explainer">
|
|
||||||
<p>
|
|
||||||
Name this connection so you can tell it apart later — the profile it creates gets this
|
|
||||||
name, and Amber will show it when asking you to approve signatures.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="field">
|
|
||||||
<label htmlFor="pair-label-input">Connection name</label>
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
id="pair-label-input"
|
|
||||||
type="text"
|
|
||||||
value={label}
|
|
||||||
onChange={(event) => setLabel(event.target.value)}
|
|
||||||
placeholder="e.g. Amber"
|
|
||||||
maxLength={60}
|
|
||||||
aria-describedby={error ? errorId : undefined}
|
|
||||||
aria-invalid={error ? true : undefined}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
{error && <ErrorText id={errorId}>{error}</ErrorText>}
|
|
||||||
</div>
|
|
||||||
<div className="modal-actions">
|
|
||||||
<Button variant="ghost" onClick={() => setPhase('choice')}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" type="submit" disabled={!canSubmit}>
|
|
||||||
Show QR code
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (phase === 'pairing') {
|
if (phase === 'pairing') {
|
||||||
return (
|
return (
|
||||||
<Modal open={open} title="Sign in with Amber" onClose={handleClose}>
|
<Modal open={open} title="Sign in with Amber" onClose={handleClose}>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ vi.mock('qrcode', () => ({
|
||||||
|
|
||||||
async function startPairingFlow(user: ReturnType<typeof userEvent.setup>) {
|
async function startPairingFlow(user: ReturnType<typeof userEvent.setup>) {
|
||||||
await user.click(screen.getByRole('button', { name: /Sign in with a signer app \(Amber\)/ }));
|
await user.click(screen.getByRole('button', { name: /Sign in with a signer app \(Amber\)/ }));
|
||||||
await user.click(screen.getByRole('button', { name: 'Show QR code' }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('CreateProfileModal', () => {
|
describe('CreateProfileModal', () => {
|
||||||
|
|
@ -93,17 +92,14 @@ describe('CreateProfileModal', () => {
|
||||||
renderWithApp(<CreateProfileModal open onClose={vi.fn()} />);
|
renderWithApp(<CreateProfileModal open onClose={vi.fn()} />);
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
// No label step (Sep 25 feedback: typing/fighting a prefilled name was
|
||||||
|
// friction). One click mints the QR with the 'Amber' seed label; the
|
||||||
|
// backend upgrades it to the account's real kind-0 display name.
|
||||||
await user.click(screen.getByRole('button', { name: /Sign in with a signer app \(Amber\)/ }));
|
await user.click(screen.getByRole('button', { name: /Sign in with a signer app \(Amber\)/ }));
|
||||||
// The label step comes first and is prefilled so Enter just works.
|
|
||||||
const nameInput = await screen.findByLabelText('Connection name');
|
|
||||||
expect(nameInput).toHaveValue('Amber');
|
|
||||||
await user.clear(nameInput);
|
|
||||||
await user.type(nameInput, 'My Phone');
|
|
||||||
await user.click(screen.getByRole('button', { name: 'Show QR code' }));
|
|
||||||
|
|
||||||
expect(await screen.findByText(/Waiting for the signer to scan/i)).toBeInTheDocument();
|
expect(await screen.findByText(/Waiting for the signer to scan/i)).toBeInTheDocument();
|
||||||
const start = backend.requests.find((r) => r.method === 'nip46_pair_start');
|
const start = backend.requests.find((r) => r.method === 'nip46_pair_start');
|
||||||
expect(start?.params.label).toBe('My Phone');
|
expect(start?.params.label).toBe('Amber');
|
||||||
expect(await screen.findByAltText('Pairing QR code')).toBeInTheDocument();
|
expect(await screen.findByAltText('Pairing QR code')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue