132 lines
4.7 KiB
TypeScript
132 lines
4.7 KiB
TypeScript
'use client';
|
||
|
||
import { Bot } from 'lucide-react';
|
||
import { useEffect, useMemo, useState } from 'react';
|
||
import { UserAvatar } from '@/components/id/user-avatar';
|
||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||
import { Skeleton } from '@/components/ui/skeleton';
|
||
import { useAuth } from '@/components/id/auth-provider';
|
||
import { ChatRoom, apiFetch } from '@/lib/api';
|
||
import { CHAT_ROOM_AVATAR_UPDATED_EVENT } from '@/lib/avatar-events';
|
||
import { useResilientBlobUrl } from '@/hooks/use-resilient-blob-url';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
interface ChatRoomAvatarDisplayProps {
|
||
room: ChatRoom;
|
||
viewerUserId: string;
|
||
token: string | null;
|
||
className?: string;
|
||
size?: 'sm' | 'md';
|
||
}
|
||
|
||
function sizeClass(size: 'sm' | 'md') {
|
||
return size === 'sm' ? 'h-9 w-9 text-xs' : 'h-11 w-11 text-sm';
|
||
}
|
||
|
||
export function ChatRoomAvatarDisplay({ room, viewerUserId, token, className, size = 'md' }: ChatRoomAvatarDisplayProps) {
|
||
const { isLoading: authLoading } = useAuth();
|
||
const [roomAvatarUrl, setRoomAvatarUrl] = useState<string | null>(null);
|
||
const [resolvingRoomAvatar, setResolvingRoomAvatar] = useState(false);
|
||
const peerMember = useMemo(
|
||
() =>
|
||
room.type === 'DIRECT' || room.type === 'E2E' || room.type === 'BOT'
|
||
? room.members.find((member) => member.userId !== viewerUserId)
|
||
: undefined,
|
||
[room.members, room.type, viewerUserId]
|
||
);
|
||
|
||
useEffect(() => {
|
||
if (!token || authLoading || !room.hasAvatar || room.type === 'DIRECT' || room.type === 'E2E' || room.type === 'BOT') {
|
||
setRoomAvatarUrl(null);
|
||
setResolvingRoomAvatar(false);
|
||
return;
|
||
}
|
||
let cancelled = false;
|
||
setResolvingRoomAvatar(true);
|
||
apiFetch<{ accessUrl: string }>(`/media/chat/${room.id}/avatar/url`, {}, token)
|
||
.then((response) => {
|
||
if (!cancelled) setRoomAvatarUrl(response.accessUrl);
|
||
})
|
||
.catch(() => {
|
||
if (!cancelled) setRoomAvatarUrl(null);
|
||
})
|
||
.finally(() => {
|
||
if (!cancelled) setResolvingRoomAvatar(false);
|
||
});
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [authLoading, room.hasAvatar, room.id, room.type, room.updatedAt, token]);
|
||
|
||
useEffect(() => {
|
||
function handleRoomAvatarUpdated(event: Event) {
|
||
const detail = (event as CustomEvent<{ roomId?: string }>).detail;
|
||
if (detail?.roomId !== room.id || !room.hasAvatar || !token || authLoading) return;
|
||
apiFetch<{ accessUrl: string }>(`/media/chat/${room.id}/avatar/url`, {}, token)
|
||
.then((response) => setRoomAvatarUrl(response.accessUrl))
|
||
.catch(() => setRoomAvatarUrl(null));
|
||
}
|
||
window.addEventListener(CHAT_ROOM_AVATAR_UPDATED_EVENT, handleRoomAvatarUpdated);
|
||
return () => window.removeEventListener(CHAT_ROOM_AVATAR_UPDATED_EVENT, handleRoomAvatarUpdated);
|
||
}, [authLoading, room.hasAvatar, room.id, token]);
|
||
|
||
const { blobUrl, isLoading: isLoadingRoomBlob } = useResilientBlobUrl(roomAvatarUrl, token, {
|
||
enabled: Boolean(room.hasAvatar && roomAvatarUrl && !authLoading),
|
||
label: `аватар чата ${room.name}`
|
||
});
|
||
|
||
if (peerMember && (room.type === 'DIRECT' || room.type === 'E2E')) {
|
||
return (
|
||
<UserAvatar
|
||
userId={peerMember.userId}
|
||
displayName={peerMember.displayName}
|
||
hasAvatar={peerMember.hasAvatar}
|
||
token={token}
|
||
isVerified={peerMember.isVerified}
|
||
verificationIcon={peerMember.verificationIcon}
|
||
className={cn(sizeClass(size), className)}
|
||
badgeSize="xs"
|
||
/>
|
||
);
|
||
}
|
||
|
||
if (room.type === 'BOT') {
|
||
return (
|
||
<Avatar className={cn(sizeClass(size), className)}>
|
||
<AvatarFallback className="bg-[#3390ec]/15 text-[#3390ec]">
|
||
<Bot className={size === 'sm' ? 'h-4 w-4' : 'h-5 w-5'} />
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
);
|
||
}
|
||
|
||
const isRoomAvatarLoading = Boolean(room.hasAvatar && (resolvingRoomAvatar || isLoadingRoomBlob || (roomAvatarUrl && !blobUrl)));
|
||
|
||
if (room.hasAvatar && isRoomAvatarLoading) {
|
||
return (
|
||
<Avatar className={cn(sizeClass(size), className)}>
|
||
<AvatarFallback className="bg-transparent p-0">
|
||
<Skeleton className="h-full w-full rounded-full" />
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
);
|
||
}
|
||
|
||
if (room.hasAvatar && blobUrl) {
|
||
return (
|
||
<Avatar className={cn(sizeClass(size), className)}>
|
||
<AvatarImage src={blobUrl} alt={room.name} />
|
||
<AvatarFallback>{room.name.slice(0, 2).toUpperCase()}</AvatarFallback>
|
||
</Avatar>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Avatar className={cn(sizeClass(size), className)}>
|
||
<AvatarFallback className={room.type === 'GENERAL' ? 'bg-[#3390ec]/15 text-[#3390ec]' : undefined}>
|
||
{room.type === 'GENERAL' ? 'В' : room.name.slice(0, 2).toUpperCase()}
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
);
|
||
}
|