Аватары заглушки

This commit is contained in:
Халимов Рустам
2026-04-02 15:26:05 +03:00
parent a42007df2d
commit 757142eda7
10 changed files with 100 additions and 80 deletions
@@ -98,6 +98,7 @@ const translations = {
sendError: 'Ошибка отправки',
// New chat
newChat: 'Новый чат',
add: 'Добавить',
menu: 'Меню',
// Date picker
months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
@@ -384,6 +385,7 @@ const translations = {
fileBtn: 'File',
sendError: 'Send error',
newChat: 'New chat',
add: 'Add',
menu: 'Menu',
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
weekDays: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
@@ -31,16 +31,16 @@ function AvatarInner({ src, name, size = 'md', className = '', online }: AvatarP
const gradientClass = generateAvatarColor(name || '');
return (
<div className={`relative shrink-0 ${className} rounded-full border-2 border-outline-variant/10 overflow-hidden`}>
<div className={`relative shrink-0 ${className} rounded-2xl border-2 border-outline-variant/10 overflow-hidden`}>
{src ? (
<img
src={src}
alt={name}
className={`${sizeClass} rounded-full object-cover`}
className={`${sizeClass} rounded-2xl object-cover`}
/>
) : (
<div
className={`${sizeClass} rounded-full ${gradientClass} flex items-center justify-center text-on-primary font-black tracking-tighter shadow-inner`}
className={`${sizeClass} rounded-2xl ${gradientClass} flex items-center justify-center text-on-primary font-black tracking-tighter shadow-inner`}
>
{initials}
</div>
@@ -45,10 +45,10 @@ export default function GlobalNavBar({ activeTab, onTabChange }: GlobalNavBarPro
</div>
<div className="mt-auto group cursor-pointer relative">
<div className="absolute -inset-1 bg-primary/20 rounded-full opacity-0 group-hover:opacity-100 blur transition-opacity" />
<div className="absolute -inset-1 bg-primary/20 rounded-2xl opacity-0 group-hover:opacity-100 blur transition-opacity" />
<img
alt="User Profile"
className="relative w-10 h-10 rounded-full border-2 border-outline-variant/10 hover:border-primary/50 transition-colors object-cover"
className="relative w-10 h-10 rounded-2xl border-2 border-outline-variant/10 hover:border-primary/50 transition-colors object-cover"
src={user?.avatar || `https://ui-avatars.com/api/?name=${user?.username || 'user'}&background=3096e5&color=fff`}
/>
</div>
@@ -40,6 +40,8 @@ import DatePicker from '../components/ui/DatePicker';
import TelegramImportModal from '../../../modules/users/presentation/components/TelegramImportModal';
import type { User as UserType, UserPresence, FriendRequest, FriendWithId } from '../../domain/types';
import { getInitials } from '../../utils/utils';
type SideView = 'main' | 'profile' | 'settings' | 'about' | 'themes' | 'friends';
interface SideMenuProps {
@@ -135,12 +137,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
onClose();
};
const initials = (user?.displayName || user?.userName || user?.username || '??')
.split(' ')
.map((w: string) => w[0])
.join('')
.slice(0, 2)
.toUpperCase();
const initials = getInitials(user?.displayName || user?.userName || user?.username || '??');
const menuItems = [
{ icon: User, label: t('myProfile'), onClick: () => { onClose(); onOpenProfile(); } },
@@ -173,12 +170,12 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
<div className="flex items-start justify-between mb-5">
{/* Avatar with glow ring */}
<div className="relative group cursor-pointer" onClick={() => { onClose(); onOpenProfile(); }}>
<div className="absolute -inset-1 bg-gradient-to-r from-accent via-purple-500 to-accent rounded-full opacity-60 blur group-hover:opacity-90 transition duration-500 animate-[spin_4s_linear_infinite]" />
<div className="absolute -inset-1 bg-gradient-to-r from-accent via-purple-500 to-accent rounded-2xl opacity-60 blur group-hover:opacity-90 transition duration-500 animate-[spin_4s_linear_infinite]" />
<div className="relative">
{user?.avatar ? (
<img src={user.avatar} alt="" className="w-[72px] h-[72px] rounded-full object-cover ring-[3px] ring-surface" />
<img src={user.avatar} alt="" className="w-[72px] h-[72px] rounded-2xl object-cover ring-[3px] ring-surface" />
) : (
<div className="w-[72px] h-[72px] rounded-full bg-linear-to-br from-primary/80 to-primary flex items-center justify-center ring-[3px] ring-surface relative overflow-hidden">
<div className="w-[72px] h-[72px] rounded-2xl bg-linear-to-br from-primary/80 to-primary flex items-center justify-center ring-[3px] ring-surface relative overflow-hidden">
<span className="relative z-10 text-2xl font-black text-on-primary drop-shadow-md">{initials}</span>
</div>
)}
@@ -518,10 +515,10 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{friendSearchResults.map((u) => (
<div key={u.id} className="flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-border/50">
{u.avatar ? (
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
<img src={u.avatar} alt="" className="w-10 h-10 rounded-xl object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm">
{(u.displayName || u.username || '?')[0].toUpperCase()}
<div className="w-10 h-10 rounded-xl bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm">
{getInitials(u.displayName || u.username || '?')}
</div>
)}
<div className="flex-1 min-w-0">
@@ -553,10 +550,10 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{friendRequests.map((req) => (
<div key={req.id} className="flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-border/50">
{req.user.avatar ? (
<img src={req.user.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
<img src={req.user.avatar} alt="" className="w-10 h-10 rounded-xl object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm">
{(req.user.displayName || req.user.username || '?')[0].toUpperCase()}
<div className="w-10 h-10 rounded-xl bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm">
{getInitials(req.user.displayName || req.user.username || '?')}
</div>
)}
<div className="flex-1 min-w-0">
@@ -598,10 +595,10 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
<div key={friend.id} className="flex items-center gap-3 p-3 rounded-xl hover:bg-white/5 transition-colors group/friend">
<div className="relative">
{friend.avatar ? (
<img src={friend.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
<img src={friend.avatar} alt="" className="w-10 h-10 rounded-xl object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm">
{(friend.displayName || friend.username || '?')[0].toUpperCase()}
<div className="w-10 h-10 rounded-xl bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm">
{getInitials(friend.displayName || friend.username || '?')}
</div>
)}
{friend.isOnline && (
@@ -122,38 +122,44 @@ export default function Sidebar() {
</div>
</div>
{/* Story circles */}
{(storyGroups.length > 0 || true) && (
<div className="flex items-center gap-4 px-6 py-2 pb-6 overflow-x-auto scrollbar-hide flex-shrink-0">
{/* Add story circle */}
<button
onClick={() => setShowCreateStory(true)}
className="flex flex-col items-center gap-1.5 flex-shrink-0 group w-[60px]"
>
<div className="w-[50px] h-[50px] rounded-full border border-dashed border-zinc-600 flex items-center justify-center group-hover:border-accent group-hover:bg-accent/10 transition-colors">
<Plus size={20} className="text-accent transition-colors" />
</div>
<span className="text-[11px] text-zinc-400 truncate w-full text-center">{t('newStory')}</span>
</button>
{/* Stories Section */}
<div className="px-6 mb-2">
<h2 className="text-[11px] font-black text-on-surface-variant/40 uppercase tracking-[0.12em] select-none">
{t('stories') || 'ИСТОРИИ'}
</h2>
</div>
{storyGroups.map((group, idx) => {
const avatar = group.user.avatarUrl || group.user.avatar;
const avatarUrl = avatar ? `${API_URL}${avatar}` : null;
const isMine = group.user.id === user?.id;
return (
<button
key={group.user.id}
onClick={() => openViewer(idx)}
className="flex flex-col items-center gap-1.5 flex-shrink-0 group w-[60px]"
>
<div className={`w-[50px] h-[50px] rounded-full flex items-center justify-center transition-transform group-hover:scale-[1.03] ${
group.hasUnviewed
? 'border-[2px] border-accent'
: isMine
? 'border border-zinc-600'
: 'border border-zinc-700'
}`}>
<div className="w-[44px] h-[44px] rounded-full flex items-center justify-center overflow-hidden">
<div className="flex items-center gap-4 px-6 py-2 pb-6 overflow-x-auto scrollbar-hide flex-shrink-0">
{/* Add story item */}
<button
onClick={() => setShowCreateStory(true)}
className="flex flex-col items-center gap-2 flex-shrink-0 group w-[62px]"
>
<div className="w-[56px] h-[56px] rounded-2xl border-2 border-dashed border-white/10 flex items-center justify-center group-hover:border-accent group-hover:bg-accent/5 transition-all">
<Plus size={22} className="text-accent/80 group-hover:text-accent transition-colors" />
</div>
<span className="text-[12px] font-medium text-zinc-400 group-hover:text-white transition-colors truncate w-full text-center">
{t('add')}
</span>
</button>
{storyGroups.map((group, idx) => {
const avatar = group.user.avatarUrl || group.user.avatar;
const avatarUrl = avatar ? `${API_URL}${avatar}` : null;
const isMine = group.user.id === user?.id;
return (
<button
key={group.user.id}
onClick={() => openViewer(idx)}
className="flex flex-col items-center gap-2 flex-shrink-0 group w-[62px]"
>
<div className={`relative p-[1.5px] rounded-[1.25rem] transition-all duration-300 ${
group.hasUnviewed
? 'bg-accent shadow-[0_0_15px_rgba(48,150,229,0.2)] scale-[1.02]'
: 'bg-white/5'
}`}>
<div className="w-[53px] h-[53px] rounded-[1.125rem] bg-surface-container-low p-1 flex items-center justify-center">
<div className="relative w-full h-full rounded-[0.9rem] overflow-hidden">
<Avatar
src={avatarUrl}
name={group.user.displayName || group.user.userName || group.user.username || '?'}
@@ -161,14 +167,14 @@ export default function Sidebar() {
/>
</div>
</div>
<span className="text-[11px] text-zinc-400 truncate w-full text-center">
{isMine ? t('myStory') : (group.user.displayName || group.user.userName || group.user.username || '?').split(' ')[0]}
</span>
</button>
);
})}
</div>
)}
</div>
<span className="text-[12px] font-medium text-zinc-400 group-hover:text-white transition-colors truncate w-full text-center">
{isMine ? t('myStory') : (group.user.displayName || group.user.userName || group.user.username || '?').split(' ')[0]}
</span>
</button>
);
})}
</div>
{/* Список чатов */}
<div className="flex-1 overflow-y-auto px-2 custom-scrollbar">