Use DiceBear bottts-neutral as the site-wide avatar fallback

Hop-State: A_06FP86GJ50CYTWZ5YZECQ0G
Hop-Proposal: R_06FP86FN4Y59N0Z8828K6W8
Hop-Task: T_06FP85JN26GSWT1GS5G0W20
Hop-Attempt: AT_06FP85JN25ZCGGGSSWN1CTR
This commit is contained in:
2026-07-14 21:47:38 -07:00
committed by Hop
parent c1647bbcfa
commit 270d5437f0
12 changed files with 81 additions and 97 deletions
+2 -5
View File
@@ -13,6 +13,7 @@ import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { ArrowLeftIcon } from '@/components/Icons';
import { Bot, Plus, Sparkles } from 'lucide-react';
import { AvatarImage } from '@/components/AvatarImage';
interface BotData {
id: string;
@@ -126,11 +127,7 @@ export default function BotsPage() {
fontSize: '18px',
}}
>
{bot.avatarUrl ? (
<img src={bot.avatarUrl} alt={bot.name} style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '50%' }} />
) : (
bot.name.charAt(0).toUpperCase()
)}
<AvatarImage avatarUrl={bot.avatarUrl} seed={bot.handle} alt={bot.name} />
</Link>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
+16 -11
View File
@@ -7,6 +7,7 @@ import { ArrowLeft, Send, Loader2, MessageCircle, Search, Plus, Trash2, MoreVert
import Link from 'next/link';
import { getProfilePath, useFormattedHandle } from '@/lib/utils/handle';
import { useRouter, useSearchParams } from 'next/navigation';
import { AvatarImage } from '@/components/AvatarImage';
interface Conversation {
id: string;
@@ -386,11 +387,11 @@ export default function ChatPage() {
<ArrowLeft size={20} />
</button>
<div className="avatar" style={{ width: '32px', height: '32px', fontSize: '14px' }}>
{selectedConversation.participant2.avatarUrl ? (
<img src={selectedConversation.participant2.avatarUrl} alt="" />
) : (
selectedConversation.participant2.displayName[0] || '?'
)}
<AvatarImage
avatarUrl={selectedConversation.participant2.avatarUrl}
seed={selectedConversation.participant2.handle}
alt={selectedConversation.participant2.displayName || selectedConversation.participant2.handle}
/>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<Link
@@ -439,11 +440,11 @@ export default function ChatPage() {
flexDirection: msg.isSentByMe ? 'row-reverse' : 'row'
}}>
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
{msg.isSentByMe ? (
user.avatarUrl ? <img src={user.avatarUrl} alt="" /> : user.displayName[0]
) : (
msg.senderAvatarUrl ? <img src={msg.senderAvatarUrl} alt="" /> : msg.senderDisplayName?.[0]
)}
<AvatarImage
avatarUrl={msg.isSentByMe ? user.avatarUrl : msg.senderAvatarUrl}
seed={msg.isSentByMe ? user.handle : msg.senderHandle}
alt={msg.isSentByMe ? user.displayName : msg.senderDisplayName || msg.senderHandle}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: msg.isSentByMe ? 'flex-end' : 'flex-start' }}>
@@ -595,7 +596,11 @@ export default function ChatPage() {
style={{ cursor: 'pointer', display: 'flex', alignItems: 'flex-start', gap: '12px' }}
>
<div className="avatar">
{conv.participant2.avatarUrl ? <img src={conv.participant2.avatarUrl} alt="" /> : conv.participant2.displayName?.[0] || '?'}
<AvatarImage
avatarUrl={conv.participant2.avatarUrl}
seed={conv.participant2.handle}
alt={conv.participant2.displayName || conv.participant2.handle}
/>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
+2 -5
View File
@@ -9,6 +9,7 @@ import { useFormattedHandle } from '@/lib/utils/handle';
import { Bot, Network, Server, EyeOff } from 'lucide-react';
import { useAuth } from '@/lib/contexts/AuthContext';
import { signedAPI } from '@/lib/api/signed-fetch';
import { AvatarImage } from '@/components/AvatarImage';
interface User {
id: string;
@@ -26,11 +27,7 @@ function UserCard({ user }: { user: User }) {
return (
<Link href={`/u/${user.handle}`} className="user-card">
<div className="avatar">
{user.avatarUrl ? (
<img src={user.avatarUrl} alt={user.displayName} />
) : (
user.displayName?.charAt(0).toUpperCase() || user.handle.charAt(0).toUpperCase()
)}
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
</div>
<div className="user-card-info">
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
+4 -26
View File
@@ -5,6 +5,7 @@ import { BellIcon } from '@/components/Icons';
import Link from 'next/link';
import { useAuth } from '@/lib/contexts/AuthContext';
import { getProfilePath } from '@/lib/utils/handle';
import { AvatarImage } from '@/components/AvatarImage';
interface NotificationActor {
id: string;
@@ -218,32 +219,9 @@ function NotificationItem({
}}
>
<Link href={actorProfilePath} style={{ flexShrink: 0 }}>
{actor?.avatarUrl ? (
<img
src={actor.avatarUrl}
alt={actor.displayName || actor.handle}
width={40}
height={40}
style={{ borderRadius: '50%', objectFit: 'cover' }}
/>
) : (
<div
style={{
width: 40,
height: 40,
borderRadius: '50%',
background: 'var(--background-tertiary)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--foreground-secondary)',
fontSize: '16px',
fontWeight: 600,
}}
>
{(actor?.displayName || actor?.handle || '?')[0].toUpperCase()}
</div>
)}
<div className="avatar">
<AvatarImage avatarUrl={actor?.avatarUrl} seed={actor?.handle || 'unknown'} alt={actor?.displayName || actor?.handle || 'Unknown user'} />
</div>
</Link>
<div style={{ flex: 1, minWidth: 0 }}>
+2 -5
View File
@@ -9,6 +9,7 @@ import { Post } from '@/lib/types';
import { Bot } from 'lucide-react';
import { useAuth } from '@/lib/contexts/AuthContext';
import { signedAPI } from '@/lib/api/signed-fetch';
import { AvatarImage } from '@/components/AvatarImage';
interface User {
id: string;
@@ -82,11 +83,7 @@ function UserCard({ user }: { user: User }) {
className="hover-bg"
>
<div className="avatar">
{user.avatarUrl ? (
<img src={user.avatarUrl} alt={user.displayName} />
) : (
(user.displayName || user.handle).charAt(0).toUpperCase()
)}
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
+4 -10
View File
@@ -5,6 +5,7 @@ import Link from 'next/link';
import { getProfilePath } from '@/lib/utils/handle';
import { ArrowLeftIcon } from '@/components/Icons';
import { UserX, Globe, Trash2 } from 'lucide-react';
import { AvatarImage } from '@/components/AvatarImage';
interface BlockedUser {
id: string;
@@ -189,16 +190,9 @@ export default function ModerationSettingsPage() {
textDecoration: 'none',
}}
>
<img
src={user.avatarUrl || '/default-avatar.png'}
alt=""
style={{
width: '40px',
height: '40px',
borderRadius: '50%',
objectFit: 'cover',
}}
/>
<div className="avatar">
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
</div>
<div>
<div style={{ fontWeight: 500 }}>
{user.displayName || user.handle}
+3 -10
View File
@@ -13,6 +13,7 @@ import { useFormattedHandle } from '@/lib/utils/handle';
import { Bot } from 'lucide-react';
import { useAuth } from '@/lib/contexts/AuthContext';
import { signedAPI } from '@/lib/api/signed-fetch';
import { AvatarImage } from '@/components/AvatarImage';
interface BotOwner {
id: string;
@@ -41,11 +42,7 @@ function UserRow({ user }: { user: UserSummary }) {
return (
<Link href={`/u/${user.handle}`} className="user-row">
<div className="avatar">
{user.avatarUrl ? (
<img src={user.avatarUrl} alt={user.displayName || user.handle} />
) : (
(user.displayName || user.handle).charAt(0).toUpperCase()
)}
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
</div>
<div className="user-row-content">
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
@@ -568,11 +565,7 @@ export default function ProfilePage() {
position: 'relative',
}}
>
{(isEditing ? profileForm.avatarUrl : user.avatarUrl) ? (
<img src={(isEditing ? profileForm.avatarUrl : user.avatarUrl) || ''} alt={user.displayName || user.handle} />
) : (
(user.displayName || user.handle).charAt(0).toUpperCase()
)}
<AvatarImage avatarUrl={isEditing ? profileForm.avatarUrl : user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
</div>
<div style={{ paddingTop: '12px', display: 'flex', gap: '8px', alignItems: 'center' }}>