| ... | @@ -14,7 +14,7 @@ import { | ... | @@ -14,7 +14,7 @@ import { |
| 14 | CircleXIcon, | 14 | CircleXIcon, |
| 15 | UserCheckIcon, | 15 | UserCheckIcon, |
| 16 | } from "lucide-react"; | 16 | } from "lucide-react"; |
| 17 | import { Children, isValidElement, type ReactNode } from "react"; | 17 | import { Children, isValidElement, type ReactNode, useState } from "react"; |
| 18 | | 18 | |
| 19 | import { cn } from "~/lib/utils"; | 19 | import { cn } from "~/lib/utils"; |
| 20 | | 20 | |
| ... | @@ -352,8 +352,9 @@ export function PullRequestActorAvatar({ | ... | @@ -352,8 +352,9 @@ export function PullRequestActorAvatar({ |
| 352 | }) { | 352 | }) { |
| 353 | const login = actor?.login ?? "ghost"; | 353 | const login = actor?.login ?? "ghost"; |
| 354 | const avatarUrl = actor?.avatarUrl ?? null; | 354 | const avatarUrl = actor?.avatarUrl ?? null; |
| 355 | return avatarUrl === null ? ( | 355 | const [failedAvatarUrl, setFailedAvatarUrl] = useState<string | null>(null); |
| 356 | // Not every host reports an avatar, so the initial stands in where none arrives. | 356 | return avatarUrl === null || failedAvatarUrl === avatarUrl ? ( |
| | 357 | // Not every host reports an avatar, and a private host may refuse the browser's request. |
| 357 | <span | 358 | <span |
| 358 | aria-hidden | 359 | aria-hidden |
| 359 | className={cn( | 360 | className={cn( |
| ... | @@ -370,6 +371,7 @@ export function PullRequestActorAvatar({ | ... | @@ -370,6 +371,7 @@ export function PullRequestActorAvatar({ |
| 370 | src={avatarUrl} | 371 | src={avatarUrl} |
| 371 | loading="lazy" | 372 | loading="lazy" |
| 372 | className={cn("size-4 shrink-0 rounded-full bg-muted object-cover", className)} | 373 | className={cn("size-4 shrink-0 rounded-full bg-muted object-cover", className)} |
| | 374 | onError={() => setFailedAvatarUrl(avatarUrl)} |
| 373 | /> | 375 | /> |
| 374 | ); | 376 | ); |
| 375 | } | 377 | } |