Show user avatars in the most-active-users leaderboard
Original task #33: The admin "Most active users" leaderboard rendered only a colored circle with the first letter of each user's name even though the API already returns each user's avatarUrl. Changes: - artifacts/teaboy-os/src/pages/admin.tsx - Added a small LeaderboardAvatar component that renders the avatar image when available and falls back to the initial when there is no avatarUrl or when the image fails to load (onError). - The avatar URL is resolved through the existing resolveServiceImageUrl helper (consistent with chat.tsx). - Kept the existing emerald circle styling as the fallback background; added overflow-hidden so the rounded image is clipped. - useEffect resets the error state when the src changes so a later valid URL still attempts to load. No API or schema changes were required (avatarUrl is already returned by GET /admin/stats and present in the generated client types). Pre-existing TypeScript errors in admin.tsx / chat.tsx / clock files are unrelated to this task and were not touched. Replit-Task-Id: a0fe21b3-7422-48ba-9731-ed8315eb9d68
This commit is contained in:
@@ -41,6 +41,36 @@ import { formatDate as formatDateUtil, formatWeekday as formatWeekdayUtil } from
|
||||
|
||||
type AdminSection = "dashboard" | "apps" | "services" | "users" | "settings";
|
||||
|
||||
function LeaderboardAvatar({
|
||||
src,
|
||||
initial,
|
||||
alt,
|
||||
}: {
|
||||
src: string | null;
|
||||
initial: string;
|
||||
alt: string;
|
||||
}) {
|
||||
const [errored, setErrored] = useState(false);
|
||||
useEffect(() => {
|
||||
setErrored(false);
|
||||
}, [src]);
|
||||
const showImage = src && !errored;
|
||||
return (
|
||||
<div className="w-8 h-8 rounded-full shrink-0 bg-emerald-100/70 text-emerald-700 flex items-center justify-center text-xs font-semibold overflow-hidden">
|
||||
{showImage ? (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
onError={() => setErrored(true)}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
initial
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type AppForm = {
|
||||
nameAr: string;
|
||||
nameEn: string;
|
||||
@@ -1353,6 +1383,7 @@ function DashboardSection({
|
||||
const displayName =
|
||||
(lang === "ar" ? u.displayNameAr : u.displayNameEn) ?? u.username;
|
||||
const initial = (displayName || u.username || "?").trim().charAt(0).toUpperCase();
|
||||
const avatarSrc = resolveServiceImageUrl(u.avatarUrl);
|
||||
return (
|
||||
<li key={u.userId}>
|
||||
<button
|
||||
@@ -1364,9 +1395,7 @@ function DashboardSection({
|
||||
<div className="text-xs font-semibold text-muted-foreground w-5 text-center shrink-0">
|
||||
{idx + 1}
|
||||
</div>
|
||||
<div className="w-8 h-8 rounded-full shrink-0 bg-emerald-100/70 text-emerald-700 flex items-center justify-center text-xs font-semibold">
|
||||
{initial}
|
||||
</div>
|
||||
<LeaderboardAvatar src={avatarSrc} initial={initial} alt={displayName} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
|
||||
Reference in New Issue
Block a user