diff --git a/frontend/src/components/common/Avatar.jsx b/frontend/src/components/common/Avatar.jsx new file mode 100644 index 0000000..10c95cd --- /dev/null +++ b/frontend/src/components/common/Avatar.jsx @@ -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 + 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 = ( + {alt + ); + + // If no status, no need for wrapper; keep DOM minimal + if (!status) { + return img; + } + + const dotColor = STATUS_COLORS[status] || STATUS_COLORS.offline; + + return ( + + {img} + + ); +}; + +export default Avatar; diff --git a/frontend/src/components/layout/Navbar.jsx b/frontend/src/components/layout/Navbar.jsx index e64e3b9..c47fc6b 100644 --- a/frontend/src/components/layout/Navbar.jsx +++ b/frontend/src/components/layout/Navbar.jsx @@ -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 = () => {
- {user.username} + {user.username}
diff --git a/frontend/src/pages/EventChatPage.jsx b/frontend/src/pages/EventChatPage.jsx index 2dce747..384134b 100644 --- a/frontend/src/pages/EventChatPage.jsx +++ b/frontend/src/pages/EventChatPage.jsx @@ -6,6 +6,7 @@ import { Send, UserPlus, Loader2, LogOut, AlertTriangle, QrCode, Edit2, Filter, import { connectSocket, disconnectSocket, getSocket } from '../services/socket'; import { eventsAPI, heatsAPI, matchesAPI } from '../services/api'; import HeatsBanner from '../components/heats/HeatsBanner'; +import Avatar from '../components/common/Avatar'; const EventChatPage = () => { const { slug } = useParams(); @@ -536,20 +537,13 @@ const EventChatPage = () => { className="flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg" >
-
- {displayUser.username} - {/* Online/Offline indicator */} -
-
+

{

)} {messages.map((message) => { - const isOwnMessage = message.userId === user.id; + const isOwnMessage = (message.userId ?? message.user_id) === user.id; return (
- {message.username}
diff --git a/frontend/src/pages/MatchChatPage.jsx b/frontend/src/pages/MatchChatPage.jsx index ec130e0..279fb4c 100644 --- a/frontend/src/pages/MatchChatPage.jsx +++ b/frontend/src/pages/MatchChatPage.jsx @@ -8,6 +8,7 @@ import { connectSocket, getSocket } from '../services/socket'; import { useWebRTC } from '../hooks/useWebRTC'; import { detectWebRTCSupport } from '../utils/webrtcDetection'; import WebRTCWarning from '../components/WebRTCWarning'; +import Avatar from '../components/common/Avatar'; const MatchChatPage = () => { const { slug } = useParams(); @@ -286,10 +287,12 @@ const MatchChatPage = () => {
- {partner.username}
@@ -355,17 +358,18 @@ const MatchChatPage = () => {
)} {messages.map((message) => { - const isOwnMessage = message.userId === user.id; + const isOwnMessage = (message.userId ?? message.user_id) === user.id; return (
- {message.username}
diff --git a/frontend/src/pages/ProfilePage.jsx b/frontend/src/pages/ProfilePage.jsx index a276531..aeacda9 100644 --- a/frontend/src/pages/ProfilePage.jsx +++ b/frontend/src/pages/ProfilePage.jsx @@ -4,6 +4,7 @@ import { authAPI } from '../services/api'; import Layout from '../components/layout/Layout'; import { User, Mail, Lock, Save, AlertCircle, CheckCircle, Loader2, Hash, Youtube, Instagram, Facebook, MapPin, Globe } from 'lucide-react'; import { COUNTRIES } from '../data/countries'; +import Avatar from '../components/common/Avatar'; const ProfilePage = () => { const { user, updateUser } = useAuth(); @@ -138,10 +139,13 @@ const ProfilePage = () => { {/* Header */}
- {user?.username}

diff --git a/frontend/src/pages/PublicProfilePage.jsx b/frontend/src/pages/PublicProfilePage.jsx index 3b6b5ed..ab6161e 100644 --- a/frontend/src/pages/PublicProfilePage.jsx +++ b/frontend/src/pages/PublicProfilePage.jsx @@ -3,6 +3,7 @@ import { useParams, Link } from 'react-router-dom'; import { authAPI, ratingsAPI } from '../services/api'; import Layout from '../components/layout/Layout'; import { User, MapPin, Globe, Hash, Youtube, Instagram, Facebook, Award, Users, Star, Calendar, Loader2, AlertCircle, ThumbsUp } from 'lucide-react'; +import Avatar from '../components/common/Avatar'; const PublicProfilePage = () => { const { username } = useParams(); @@ -86,10 +87,12 @@ const PublicProfilePage = () => { {/* Profile Header */}
- {profile.username}

@@ -263,10 +266,12 @@ const PublicProfilePage = () => {
{/* Rater Info */} - {rating.rater.username}