diff --git a/apps/wallet/src/App.tsx b/apps/wallet/src/App.tsx index 5855899..5e6b927 100644 --- a/apps/wallet/src/App.tsx +++ b/apps/wallet/src/App.tsx @@ -195,6 +195,18 @@ function App() { } }; + const renameIdentity = async (identity: LocalIdentitySummary, nickname?: string) => { + await walletAdapter.setLabel(identity.localId, nickname); + await refresh(); + setNotice({ + kind: 'success', + message: + nickname === undefined + ? 'Local identity nickname removed.' + : `Local identity nickname changed to “${nickname}”.`, + }); + }; + const issueDevice = async ( identity: LocalIdentitySummary, options: IssueDeviceTransferOptions, @@ -392,7 +404,7 @@ function App() { Identity wallet

- Local labels and app scopes never leave this wallet. + Local nicknames and app scopes never leave this wallet.

@@ -485,6 +497,7 @@ function App() { onRetryRegistration={() => void retryRegistration(selectedIdentity)} clearingHistory={clearingHistoryId === selectedIdentity.localId} onClearHistory={() => clearAuthorizationHistory(selectedIdentity)} + onRename={(nickname) => renameIdentity(selectedIdentity, nickname)} onAddDevice={() => setFlow({ type: 'issue-device', identity: selectedIdentity })} onActivateDevice={() => activateDevice(selectedIdentity)} onSelfRevokeDevice={() => diff --git a/apps/wallet/src/components/DeviceManagement.tsx b/apps/wallet/src/components/DeviceManagement.tsx index 52843db..49f87cc 100644 --- a/apps/wallet/src/components/DeviceManagement.tsx +++ b/apps/wallet/src/components/DeviceManagement.tsx @@ -146,7 +146,7 @@ export function IssueDeviceModal({ identity, onClose, onIssue }: IssueDeviceModa
void; clearingHistory: boolean; onClearHistory: () => Promise; + onRename: (nickname?: string) => Promise; onAddDevice: () => void; onActivateDevice: () => Promise; onSelfRevokeDevice: () => void; @@ -53,6 +55,7 @@ export function IdentityDetail({ onRetryRegistration, clearingHistory, onClearHistory, + onRename, onAddDevice, onActivateDevice, onSelfRevokeDevice, @@ -62,6 +65,10 @@ export function IdentityDetail({ const active = identity.localState === 'active'; const registeredActions = canUseRegisteredIdentityActions(identity); const [confirmClearHistory, setConfirmClearHistory] = useState(false); + const [editingNickname, setEditingNickname] = useState(false); + const [nickname, setNickname] = useState(identity.label ?? ''); + const [renaming, setRenaming] = useState(false); + const [renameError, setRenameError] = useState(); const [now, setNow] = useState(() => Math.floor(Date.now() / 1_000)); const deviceCapabilities = deviceManagementCapabilities(identity, now); @@ -74,6 +81,34 @@ export function IdentityDetail({ return () => window.clearTimeout(handle); }, [identity.device, now]); + useEffect(() => { + if (!editingNickname) setNickname(identity.label ?? ''); + }, [editingNickname, identity.label]); + + const saveNickname = async () => { + const normalized = nickname.trim(); + if (normalized.length > 128) { + setRenameError('The nickname cannot exceed 128 characters.'); + return; + } + setRenaming(true); + setRenameError(undefined); + try { + await onRename(normalized === '' ? undefined : normalized); + setEditingNickname(false); + } catch (error) { + setRenameError(error instanceof Error ? error.message : 'The nickname could not be saved.'); + } finally { + setRenaming(false); + } + }; + + const cancelNickname = () => { + setNickname(identity.label ?? ''); + setRenameError(undefined); + setEditingNickname(false); + }; + const clearHistory = async () => { if (!confirmClearHistory) { setConfirmClearHistory(true); @@ -101,12 +136,71 @@ export function IdentityDetail({

Local identity

-

- {identity.label ?? 'Untitled identity'} -

+
+

+ {identity.label ?? 'Untitled identity'} +

+ {!editingNickname && ( + + )} +
+ {editingNickname && ( +
{ + event.preventDefault(); + void saveNickname(); + }} + > + +
+ setNickname(event.target.value)} + maxLength={128} + autoComplete="off" + placeholder="Nickname on this wallet" + disabled={renaming} + className="min-w-48 flex-1 rounded-lg border border-slate-300 px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500" + /> + + +
+

+ Stored only in this wallet. It does not change your identity or appear in + proofs. +

+ {renameError !== undefined && ( +

+ {renameError} +

+ )} +
+ )}