/**
* 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 (
setImgFailed(true)}
/>
);
}
return (