import { useQuery } from '@tanstack/react-query'; import { ShieldAlert } from 'lucide-react'; import { Copyable } from '#/components/molecules/copyable.jsx'; import { shortDid } from '#/lib/format.js'; import { identityOf } from '#/lib/identity.js'; import { cn } from '#/lib/utils.js'; /** * Who a DID is, resolved once per DID for as long as the tab is open. Several * screens name the same few accounts, and one query key serves all of them. * @param {string} did */ function useIdentity(did) { return useQuery({ queryKey: ['identity', did], queryFn: () => identityOf(did), retry: false, staleTime: Number.POSITIVE_INFINITY, enabled: Boolean(did), }); } /** * What a hover says about a handle: the DID it stands for, and whether the two * agree. * @param {string} did @param {boolean} verified * @returns {string} */ function verificationNote(did, verified) { return `${did}\n${ verified ? 'This handle resolves back to this DID.' : 'Claimed by the DID document but not confirmed.' }`; } /** * A DID, shown as whoever holds it. * * The DID is what the protocol addresses and the handle is what a reader knows, * so the handle is the label and the DID is what the click copies. A handle the * document claims but which does not resolve back is marked: anybody may write * any handle into their own DID document, and an unconfirmed one shown plainly * is somebody else's name on this page. */ export function Identity({ did, className, inline = false }) { const { data, isPending } = useIdentity(did); if (!did) return null; // The DID until the lookup settles, so the line does not reflow under a // reader who is already looking at it. if (isPending || !data?.handle) { return inline ? ( {shortDid(did)} ) : ( {shortDid(did)} ); } // Inside a sentence a handle is a name, so it is set in the surrounding face // and carries no copy affordance: that control keeps its width whether or not // it is showing, which in running text is a hole between the name and the // word after it. if (inline) { return ( {data.handle} {!data.verified && ( )} ); } return ( {data.handle} {!data.verified && ( )} ); } /** * The same identity as a bare string, for a heading that reads as a sentence. * Falls back to the shortened DID, so it never renders empty. * @param {string} did * @returns {string} */ export function useIdentityLabel(did) { const { data } = useIdentity(did); return data?.handle || shortDid(did || ''); }