feat(ui): unify avatars across navbar, profiles, event/match chat

- Add reusable Avatar with fallback, status dot, ring
- Replace <img> uses in Navbar, Profile, PublicProfile
- Use Avatar in MatchChatPage and EventChatPage messages and sidebars
- Fix own-message detection for snake_case payloads
This commit is contained in:
Radosław Gierwiało
2025-11-15 23:08:00 +01:00
parent 6a17143ce1
commit 38adf1e5a5
6 changed files with 118 additions and 42 deletions

View File

@@ -0,0 +1,71 @@
import React from 'react';
const STATUS_COLORS = {
online: 'bg-green-500',
offline: 'bg-gray-400',
busy: 'bg-yellow-500',
};
const Avatar = ({
src,
username = '',
alt,
size = 32, // number (px) or string with Tailwind classes
className = '', // applied to <img>
containerClassName = '', // applied to wrapper
rounded = true,
ring = false,
ringColor = 'ring-white',
status = null, // 'online' | 'offline' | 'busy' | null
title,
onClick,
}) => {
const [imgSrc, setImgSrc] = React.useState(src || '');
const isNumericSize = typeof size === 'number';
const style = isNumericSize ? { width: size, height: size } : undefined;
const sizeClass = isNumericSize ? '' : size; // e.g. 'w-8 h-8'
const fallback = `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(
username || 'user'
)}`;
const handleError = (e) => {
if (e.currentTarget.src !== fallback) {
setImgSrc(fallback);
}
};
const img = (
<img
src={imgSrc || fallback}
alt={alt || username || 'avatar'}
className={`${sizeClass} ${rounded ? 'rounded-full' : ''} bg-gray-100 object-cover ${
ring ? `ring-2 ${ringColor}` : ''
} ${className}`}
style={style}
loading="lazy"
onError={handleError}
title={title || username}
/>
);
// If no status, no need for wrapper; keep DOM minimal
if (!status) {
return img;
}
const dotColor = STATUS_COLORS[status] || STATUS_COLORS.offline;
return (
<span className={`relative inline-block ${containerClassName}`} onClick={onClick}>
{img}
<span
className={`absolute bottom-0 right-0 block h-3 w-3 ${dotColor} rounded-full ring-2 ring-white`}
aria-hidden="true"
/>
</span>
);
};
export default Avatar;

View File

@@ -1,6 +1,7 @@
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { Video, LogOut, User, History, Users } from 'lucide-react';
import Avatar from '../common/Avatar';
import { useState, useEffect } from 'react';
import { matchesAPI } from '../../services/api';
import { connectSocket, disconnectSocket, getSocket } from '../../services/socket';
@@ -99,11 +100,7 @@ const Navbar = () => {
</Link>
<div className="flex items-center space-x-3">
<img
src={user.avatar}
alt={user.username}
className="w-8 h-8 rounded-full"
/>
<Avatar src={user?.avatar} username={user.username} size={32} />
<span className="text-sm font-medium text-gray-700">{user.username}</span>
</div>