chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { identityOf } from '#/lib/identity.js';
import { profileOf } from '#/lib/profile.js';
import { cn } from '#/lib/utils.js';
/**
* An account's picture, or the first letter of whatever it is called.
*
* The letter comes from the handle rather than the DID, so an account with no
* picture still reads as itself. Both lookups share the keys the rest of the
* app uses, so a DID already named somewhere on the page costs nothing here.
*
* @param {Object} props
* @param {string} props.did
* @param {number} [props.size]
* @param {string} [props.className]
*/
export function ActorAvatar({ did, size = 20, className }) {
const [broken, setBroken] = useState(false);
const { data: profile } = useQuery({
queryKey: ['profile', did],
queryFn: () => profileOf(did),
retry: false,
staleTime: Number.POSITIVE_INFINITY,
enabled: Boolean(did),
});
const { data: identity } = useQuery({
queryKey: ['identity', did],
queryFn: () => identityOf(did),
retry: false,
staleTime: Number.POSITIVE_INFINITY,
enabled: Boolean(did),
});
if (!did) return null;
const letter = (identity?.handle || did.replace('did:plc:', ''))
.slice(0, 1)
.toUpperCase();
const src = !broken && profile?.avatar;
return (
<span
className={cn(
'inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted font-semibold text-faint',
className,
)}
style={{ width: size, height: size, fontSize: Math.round(size * 0.5) }}
>
{src ? (
<img
src={profile.avatar}
alt=""
width={size}
height={size}
className="size-full object-cover"
onError={() => setBroken(true)}
/>
) : (
letter
)}
</span>
);
}