fix(desktop): degrade missing avatar image to letter circle

Avatar rendered a bare <img> with no error handling, so an avatar_url whose
storage object is unreachable (e.g. a 404 after the server move) showed a
broken image instead of the coloured letter-circle fallback. Track an onError
flag and fall back to the circle; reset it when the URL changes so a fresh
valid avatar is retried. This is client-side resilience only — it does not
restore a genuinely missing storage object.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
byGalax
2026-06-02 23:02:07 +02:00
parent 9c5456b492
commit b364c53c61
+10 -1
View File
@@ -1,6 +1,8 @@
// Reusable avatar that prefers an uploaded image and falls back to a coloured // Reusable avatar that prefers an uploaded image and falls back to a coloured
// letter circle. Use this everywhere the app needs to render a profile. // letter circle. Use this everywhere the app needs to render a profile.
import { useEffect, useState } from 'react';
import { useCachedAvatarUrl } from '../lib/avatarCache'; import { useCachedAvatarUrl } from '../lib/avatarCache';
interface Props { interface Props {
@@ -27,7 +29,13 @@ export function Avatar({
loading = 'lazy', loading = 'lazy',
}: Props) { }: Props) {
const effectiveUrl = useCachedAvatarUrl(url); const effectiveUrl = useCachedAvatarUrl(url);
if (effectiveUrl) { // If the image URL is non-empty but unreachable (e.g. the storage object is
// missing / 404s), the bare <img> would render broken with no fallback.
// Track a load error and degrade to the letter circle instead. Reset on URL
// change so a fresh, valid avatar is retried.
const [failed, setFailed] = useState(false);
useEffect(() => setFailed(false), [effectiveUrl]);
if (effectiveUrl && !failed) {
return ( return (
<img <img
src={effectiveUrl} src={effectiveUrl}
@@ -35,6 +43,7 @@ export function Avatar({
className={'shrink-0 rounded-full object-cover ' + className} className={'shrink-0 rounded-full object-cover ' + className}
draggable={false} draggable={false}
loading={loading} loading={loading}
onError={() => setFailed(true)}
/> />
); );
} }