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:
@@ -13,6 +13,7 @@ import { useRouter } from 'next/navigation';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ArrowLeftIcon } from '@/components/Icons';
|
import { ArrowLeftIcon } from '@/components/Icons';
|
||||||
import { Bot, Plus, Sparkles } from 'lucide-react';
|
import { Bot, Plus, Sparkles } from 'lucide-react';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface BotData {
|
interface BotData {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -126,11 +127,7 @@ export default function BotsPage() {
|
|||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{bot.avatarUrl ? (
|
<AvatarImage avatarUrl={bot.avatarUrl} seed={bot.handle} alt={bot.name} />
|
||||||
<img src={bot.avatarUrl} alt={bot.name} style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '50%' }} />
|
|
||||||
) : (
|
|
||||||
bot.name.charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
|
||||||
|
|||||||
+16
-11
@@ -7,6 +7,7 @@ import { ArrowLeft, Send, Loader2, MessageCircle, Search, Plus, Trash2, MoreVert
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { getProfilePath, useFormattedHandle } from '@/lib/utils/handle';
|
import { getProfilePath, useFormattedHandle } from '@/lib/utils/handle';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface Conversation {
|
interface Conversation {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -386,11 +387,11 @@ export default function ChatPage() {
|
|||||||
<ArrowLeft size={20} />
|
<ArrowLeft size={20} />
|
||||||
</button>
|
</button>
|
||||||
<div className="avatar" style={{ width: '32px', height: '32px', fontSize: '14px' }}>
|
<div className="avatar" style={{ width: '32px', height: '32px', fontSize: '14px' }}>
|
||||||
{selectedConversation.participant2.avatarUrl ? (
|
<AvatarImage
|
||||||
<img src={selectedConversation.participant2.avatarUrl} alt="" />
|
avatarUrl={selectedConversation.participant2.avatarUrl}
|
||||||
) : (
|
seed={selectedConversation.participant2.handle}
|
||||||
selectedConversation.participant2.displayName[0] || '?'
|
alt={selectedConversation.participant2.displayName || selectedConversation.participant2.handle}
|
||||||
)}
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Link
|
<Link
|
||||||
@@ -439,11 +440,11 @@ export default function ChatPage() {
|
|||||||
flexDirection: msg.isSentByMe ? 'row-reverse' : 'row'
|
flexDirection: msg.isSentByMe ? 'row-reverse' : 'row'
|
||||||
}}>
|
}}>
|
||||||
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
||||||
{msg.isSentByMe ? (
|
<AvatarImage
|
||||||
user.avatarUrl ? <img src={user.avatarUrl} alt="" /> : user.displayName[0]
|
avatarUrl={msg.isSentByMe ? user.avatarUrl : msg.senderAvatarUrl}
|
||||||
) : (
|
seed={msg.isSentByMe ? user.handle : msg.senderHandle}
|
||||||
msg.senderAvatarUrl ? <img src={msg.senderAvatarUrl} alt="" /> : msg.senderDisplayName?.[0]
|
alt={msg.isSentByMe ? user.displayName : msg.senderDisplayName || msg.senderHandle}
|
||||||
)}
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: msg.isSentByMe ? 'flex-end' : 'flex-start' }}>
|
<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' }}
|
style={{ cursor: 'pointer', display: 'flex', alignItems: 'flex-start', gap: '12px' }}
|
||||||
>
|
>
|
||||||
<div className="avatar">
|
<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>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useFormattedHandle } from '@/lib/utils/handle';
|
|||||||
import { Bot, Network, Server, EyeOff } from 'lucide-react';
|
import { Bot, Network, Server, EyeOff } from 'lucide-react';
|
||||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||||
import { signedAPI } from '@/lib/api/signed-fetch';
|
import { signedAPI } from '@/lib/api/signed-fetch';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -26,11 +27,7 @@ function UserCard({ user }: { user: User }) {
|
|||||||
return (
|
return (
|
||||||
<Link href={`/u/${user.handle}`} className="user-card">
|
<Link href={`/u/${user.handle}`} className="user-card">
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
{user.avatarUrl ? (
|
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
|
||||||
<img src={user.avatarUrl} alt={user.displayName} />
|
|
||||||
) : (
|
|
||||||
user.displayName?.charAt(0).toUpperCase() || user.handle.charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="user-card-info">
|
<div className="user-card-info">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { BellIcon } from '@/components/Icons';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||||
import { getProfilePath } from '@/lib/utils/handle';
|
import { getProfilePath } from '@/lib/utils/handle';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface NotificationActor {
|
interface NotificationActor {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -218,32 +219,9 @@ function NotificationItem({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href={actorProfilePath} style={{ flexShrink: 0 }}>
|
<Link href={actorProfilePath} style={{ flexShrink: 0 }}>
|
||||||
{actor?.avatarUrl ? (
|
<div className="avatar">
|
||||||
<img
|
<AvatarImage avatarUrl={actor?.avatarUrl} seed={actor?.handle || 'unknown'} alt={actor?.displayName || actor?.handle || 'Unknown user'} />
|
||||||
src={actor.avatarUrl}
|
</div>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Post } from '@/lib/types';
|
|||||||
import { Bot } from 'lucide-react';
|
import { Bot } from 'lucide-react';
|
||||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||||
import { signedAPI } from '@/lib/api/signed-fetch';
|
import { signedAPI } from '@/lib/api/signed-fetch';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -82,11 +83,7 @@ function UserCard({ user }: { user: User }) {
|
|||||||
className="hover-bg"
|
className="hover-bg"
|
||||||
>
|
>
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
{user.avatarUrl ? (
|
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
|
||||||
<img src={user.avatarUrl} alt={user.displayName} />
|
|
||||||
) : (
|
|
||||||
(user.displayName || user.handle).charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
|||||||
import { getProfilePath } from '@/lib/utils/handle';
|
import { getProfilePath } from '@/lib/utils/handle';
|
||||||
import { ArrowLeftIcon } from '@/components/Icons';
|
import { ArrowLeftIcon } from '@/components/Icons';
|
||||||
import { UserX, Globe, Trash2 } from 'lucide-react';
|
import { UserX, Globe, Trash2 } from 'lucide-react';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface BlockedUser {
|
interface BlockedUser {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -189,16 +190,9 @@ export default function ModerationSettingsPage() {
|
|||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<div className="avatar">
|
||||||
src={user.avatarUrl || '/default-avatar.png'}
|
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
|
||||||
alt=""
|
</div>
|
||||||
style={{
|
|
||||||
width: '40px',
|
|
||||||
height: '40px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
objectFit: 'cover',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 500 }}>
|
<div style={{ fontWeight: 500 }}>
|
||||||
{user.displayName || user.handle}
|
{user.displayName || user.handle}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useFormattedHandle } from '@/lib/utils/handle';
|
|||||||
import { Bot } from 'lucide-react';
|
import { Bot } from 'lucide-react';
|
||||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||||
import { signedAPI } from '@/lib/api/signed-fetch';
|
import { signedAPI } from '@/lib/api/signed-fetch';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
interface BotOwner {
|
interface BotOwner {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -41,11 +42,7 @@ function UserRow({ user }: { user: UserSummary }) {
|
|||||||
return (
|
return (
|
||||||
<Link href={`/u/${user.handle}`} className="user-row">
|
<Link href={`/u/${user.handle}`} className="user-row">
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
{user.avatarUrl ? (
|
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
|
||||||
<img src={user.avatarUrl} alt={user.displayName || user.handle} />
|
|
||||||
) : (
|
|
||||||
(user.displayName || user.handle).charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="user-row-content">
|
<div className="user-row-content">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
@@ -568,11 +565,7 @@ export default function ProfilePage() {
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(isEditing ? profileForm.avatarUrl : user.avatarUrl) ? (
|
<AvatarImage avatarUrl={isEditing ? profileForm.avatarUrl : user.avatarUrl} seed={user.handle} alt={user.displayName || user.handle} />
|
||||||
<img src={(isEditing ? profileForm.avatarUrl : user.avatarUrl) || ''} alt={user.displayName || user.handle} />
|
|
||||||
) : (
|
|
||||||
(user.displayName || user.handle).charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ paddingTop: '12px', display: 'flex', gap: '8px', alignItems: 'center' }}>
|
<div style={{ paddingTop: '12px', display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { getDiceBearAvatarUrl } from './AvatarImage';
|
||||||
|
|
||||||
|
describe('getDiceBearAvatarUrl', () => {
|
||||||
|
it('uses the site-wide bottts-neutral style and a stable encoded handle seed', () => {
|
||||||
|
expect(getDiceBearAvatarUrl('cyph3r@node.example')).toBe(
|
||||||
|
'https://api.dicebear.com/9.x/bottts-neutral/svg?seed=cyph3r%40node.example',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, type ImgHTMLAttributes } from 'react';
|
||||||
|
|
||||||
|
export function getDiceBearAvatarUrl(seed: string): string {
|
||||||
|
return `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${encodeURIComponent(seed)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvatarImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {
|
||||||
|
avatarUrl?: string | null;
|
||||||
|
seed: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AvatarImage({ avatarUrl, seed, alt = '', onError, ...props }: AvatarImageProps) {
|
||||||
|
const [failedAvatarUrl, setFailedAvatarUrl] = useState<string | null>(null);
|
||||||
|
const customAvatar = avatarUrl?.trim();
|
||||||
|
const src = customAvatar && failedAvatarUrl !== customAvatar ? customAvatar : getDiceBearAvatarUrl(seed);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
{...props}
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
onError={(event) => {
|
||||||
|
onError?.(event);
|
||||||
|
if (customAvatar && failedAvatarUrl !== customAvatar) setFailedAvatarUrl(customAvatar);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { useFormattedHandle } from '@/lib/utils/handle';
|
|||||||
import { useDomain } from '@/lib/contexts/ConfigContext';
|
import { useDomain } from '@/lib/contexts/ConfigContext';
|
||||||
import { signedAPI } from '@/lib/api/signed-fetch';
|
import { signedAPI } from '@/lib/api/signed-fetch';
|
||||||
import type { LinkPreviewData } from '@/lib/media/linkPreview';
|
import type { LinkPreviewData } from '@/lib/media/linkPreview';
|
||||||
|
import { AvatarImage } from '@/components/AvatarImage';
|
||||||
|
|
||||||
// Component for link preview image that hides on error
|
// Component for link preview image that hides on error
|
||||||
function LinkPreviewImage({ src, alt }: { src: string; alt: string }) {
|
function LinkPreviewImage({ src, alt }: { src: string; alt: string }) {
|
||||||
@@ -638,11 +639,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
|
|||||||
<div className="post-header">
|
<div className="post-header">
|
||||||
<Link href={`/u/${profileHandle}`} className="avatar-link" onClick={(e) => e.stopPropagation()}>
|
<Link href={`/u/${profileHandle}`} className="avatar-link" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
{post.author.avatarUrl ? (
|
<AvatarImage avatarUrl={post.author.avatarUrl} seed={post.author.handle} alt={post.author.displayName || post.author.handle} />
|
||||||
<img src={post.author.avatarUrl} alt={post.author.displayName || ''} />
|
|
||||||
) : (
|
|
||||||
post.author.displayName?.charAt(0).toUpperCase() || post.author.handle.charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="post-author">
|
<div className="post-author">
|
||||||
@@ -720,11 +717,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
|
|||||||
<div className="post-header">
|
<div className="post-header">
|
||||||
<Link href={`/u/${profileHandle}`} className="avatar-link" onClick={(e) => e.stopPropagation()}>
|
<Link href={`/u/${profileHandle}`} className="avatar-link" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="avatar">
|
<div className="avatar">
|
||||||
{post.author.avatarUrl ? (
|
<AvatarImage avatarUrl={post.author.avatarUrl} seed={post.author.handle} alt={post.author.displayName || post.author.handle} />
|
||||||
<img src={post.author.avatarUrl} alt={post.author.displayName} />
|
|
||||||
) : (
|
|
||||||
post.author.displayName?.charAt(0).toUpperCase() || post.author.handle.charAt(0).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="post-author">
|
<div className="post-author">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { AvatarImage } from './AvatarImage';
|
||||||
|
|
||||||
interface Admin {
|
interface Admin {
|
||||||
handle: string;
|
handle: string;
|
||||||
@@ -136,11 +137,7 @@ export function RightSidebar() {
|
|||||||
style={{ display: 'flex', alignItems: 'center', gap: '10px', textDecoration: 'none', color: 'inherit' }}
|
style={{ display: 'flex', alignItems: 'center', gap: '10px', textDecoration: 'none', color: 'inherit' }}
|
||||||
>
|
>
|
||||||
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
||||||
{admin.avatarUrl ? (
|
<AvatarImage avatarUrl={admin.avatarUrl} seed={admin.handle} alt={admin.displayName || admin.handle} />
|
||||||
<img src={admin.avatarUrl} alt={admin.displayName || admin.handle} />
|
|
||||||
) : (
|
|
||||||
(admin.displayName?.charAt(0) || admin.handle.charAt(0)).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 500, fontSize: '14px' }}>
|
<div style={{ fontWeight: 500, fontSize: '14px' }}>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { HomeIcon, SearchIcon, BellIcon, UserIcon, ShieldIcon, SettingsIcon, Bot
|
|||||||
import { useFormattedHandle } from '@/lib/utils/handle';
|
import { useFormattedHandle } from '@/lib/utils/handle';
|
||||||
import { Check, ChevronDown, LogOut, Plus, Settings2 } from 'lucide-react';
|
import { Check, ChevronDown, LogOut, Plus, Settings2 } from 'lucide-react';
|
||||||
import { AuthScreen } from '@/app/login/page';
|
import { AuthScreen } from '@/app/login/page';
|
||||||
|
import { AvatarImage } from './AvatarImage';
|
||||||
// import { IdentityUnlockPrompt } from './IdentityUnlockPrompt'; // Moved to LayoutWrapper
|
// import { IdentityUnlockPrompt } from './IdentityUnlockPrompt'; // Moved to LayoutWrapper
|
||||||
|
|
||||||
function shortHandle(handle: string) {
|
function shortHandle(handle: string) {
|
||||||
@@ -327,11 +328,7 @@ export function Sidebar() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
||||||
{account.avatarUrl ? (
|
<AvatarImage avatarUrl={account.avatarUrl} seed={account.handle} alt={account.displayName || account.handle} />
|
||||||
<img src={account.avatarUrl} alt={account.displayName || account.handle} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
||||||
) : (
|
|
||||||
(account.displayName?.charAt(0) || account.handle.charAt(0)).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ minWidth: 0, flex: 1, display: 'grid', gap: '2px' }}>
|
<div style={{ minWidth: 0, flex: 1, display: 'grid', gap: '2px' }}>
|
||||||
<div style={{ fontWeight: 700, fontSize: '15px', lineHeight: 1.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
<div style={{ fontWeight: 700, fontSize: '15px', lineHeight: 1.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
@@ -418,11 +415,7 @@ export function Sidebar() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
<div className="avatar avatar-sm" style={{ flexShrink: 0 }}>
|
||||||
{user.avatarUrl ? (
|
<AvatarImage avatarUrl={user.avatarUrl} seed={user.handle} alt={user.displayName} />
|
||||||
<img src={user.avatarUrl} alt={user.displayName} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
||||||
) : (
|
|
||||||
(user.displayName?.charAt(0) || user.handle.charAt(0)).toUpperCase()
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ minWidth: 0, flex: 1, textAlign: 'left' }}>
|
<div style={{ minWidth: 0, flex: 1, textAlign: 'left' }}>
|
||||||
<div style={{ fontWeight: 600, fontSize: '14px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{user.displayName}</div>
|
<div style={{ fontWeight: 600, fontSize: '14px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{user.displayName}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user