/** * A user's avatar, or their initial. * * @module */ import { useContext, useState } from "react"; import { API_URL } from "../../config/api.ts"; import { AvatarOverrideContext } from "../../contexts/ws/AvatarOverrideContext.ts"; /** The props of {@link Avatar}. */ export interface AvatarProps { /** The user's id. */ userId: string; /** The user's name, for the alt text and the initial. */ username: string; /** Whether they have an avatar, as the surrounding data says. */ hasAvatar: boolean; /** The side, in px; 36 by default. */ size?: number; /** Cache-busts the avatar URL. */ version?: number; } /** * Renders a user's avatar, or their initial when they have none or it fails to * load. A live `user_updated` override (avatar add/remove/change) wins over the * possibly-stale props the surrounding list was rendered with. */ export function Avatar( { userId, username, hasAvatar, size = 36, version }: AvatarProps, ): React.JSX.Element { const [imgFailed, setImgFailed] = useState(false); const sizeStyle = { width: size, height: size }; // A live `user_updated` override (avatar add/remove/change) wins over the // possibly-stale props the surrounding list was rendered with. const override = useContext(AvatarOverrideContext)[userId]; const effectiveHasAvatar = override ? override.hasAvatar : hasAvatar; const effectiveVersion = override ? override.version : version; // A new image (different version/presence) is a fresh thing to try — clear // any prior load failure during render rather than in an effect. const imgKey = `${effectiveHasAvatar}:${effectiveVersion ?? ""}`; const [prevImgKey, setPrevImgKey] = useState(imgKey); if (prevImgKey !== imgKey) { setPrevImgKey(imgKey); setImgFailed(false); } if (effectiveHasAvatar && !imgFailed) { const src = effectiveVersion ? `${API_URL}/api/avatars/${userId}?v=${effectiveVersion}` : `${API_URL}/api/avatars/${userId}`; return ( {username} setImgFailed(true)} /> ); } return (
{username.charAt(0).toUpperCase()}
); }