~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
65 lines · 1.8 KB Raw History
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>
  );
}