From 757142eda7d1e877073046aea865a6034236e148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Thu, 2 Apr 2026 15:26:05 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=B2=D0=B0=D1=82=D0=B0=D1=80=D1=8B=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-web/src/core/infrastructure/i18n.ts | 2 + .../presentation/components/ui/Avatar.tsx | 6 +- .../presentation/layouts/GlobalNavBar.tsx | 4 +- .../core/presentation/layouts/SideMenu.tsx | 33 ++++---- .../src/core/presentation/layouts/Sidebar.tsx | 84 ++++++++++--------- .../presentation/components/ChatListItem.tsx | 2 +- .../presentation/components/ChatView.tsx | 4 +- .../presentation/components/MessageBubble.tsx | 16 ++-- .../presentation/components/NewChatModal.tsx | 24 ++++-- .../presentation/components/UserProfile.tsx | 5 +- 10 files changed, 100 insertions(+), 80 deletions(-) diff --git a/client-web/src/core/infrastructure/i18n.ts b/client-web/src/core/infrastructure/i18n.ts index e962aa0..b1835df 100644 --- a/client-web/src/core/infrastructure/i18n.ts +++ b/client-web/src/core/infrastructure/i18n.ts @@ -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'], diff --git a/client-web/src/core/presentation/components/ui/Avatar.tsx b/client-web/src/core/presentation/components/ui/Avatar.tsx index a3a904b..44022ce 100644 --- a/client-web/src/core/presentation/components/ui/Avatar.tsx +++ b/client-web/src/core/presentation/components/ui/Avatar.tsx @@ -31,16 +31,16 @@ function AvatarInner({ src, name, size = 'md', className = '', online }: AvatarP const gradientClass = generateAvatarColor(name || ''); return ( -
+
{src ? ( {name} ) : (
{initials}
diff --git a/client-web/src/core/presentation/layouts/GlobalNavBar.tsx b/client-web/src/core/presentation/layouts/GlobalNavBar.tsx index a679437..76ee18b 100644 --- a/client-web/src/core/presentation/layouts/GlobalNavBar.tsx +++ b/client-web/src/core/presentation/layouts/GlobalNavBar.tsx @@ -45,10 +45,10 @@ export default function GlobalNavBar({ activeTab, onTabChange }: GlobalNavBarPro
-
+
User Profile
diff --git a/client-web/src/core/presentation/layouts/SideMenu.tsx b/client-web/src/core/presentation/layouts/SideMenu.tsx index 700aa0d..6b3dc68 100644 --- a/client-web/src/core/presentation/layouts/SideMenu.tsx +++ b/client-web/src/core/presentation/layouts/SideMenu.tsx @@ -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
{/* Avatar with glow ring */}
{ onClose(); onOpenProfile(); }}> -
+
{user?.avatar ? ( - + ) : ( -
+
{initials}
)} @@ -518,10 +515,10 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro {friendSearchResults.map((u) => (
{u.avatar ? ( - + ) : ( -
- {(u.displayName || u.username || '?')[0].toUpperCase()} +
+ {getInitials(u.displayName || u.username || '?')}
)}
@@ -553,10 +550,10 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro {friendRequests.map((req) => (
{req.user.avatar ? ( - + ) : ( -
- {(req.user.displayName || req.user.username || '?')[0].toUpperCase()} +
+ {getInitials(req.user.displayName || req.user.username || '?')}
)}
@@ -598,10 +595,10 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
{friend.avatar ? ( - + ) : ( -
- {(friend.displayName || friend.username || '?')[0].toUpperCase()} +
+ {getInitials(friend.displayName || friend.username || '?')}
)} {friend.isOnline && ( diff --git a/client-web/src/core/presentation/layouts/Sidebar.tsx b/client-web/src/core/presentation/layouts/Sidebar.tsx index 79ba782..13288cc 100644 --- a/client-web/src/core/presentation/layouts/Sidebar.tsx +++ b/client-web/src/core/presentation/layouts/Sidebar.tsx @@ -122,38 +122,44 @@ export default function Sidebar() {
- {/* Story circles */} - {(storyGroups.length > 0 || true) && ( -
- {/* Add story circle */} - + {/* Stories Section */} +
+

+ {t('stories') || 'ИСТОРИИ'} +

+
- {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 ( - + + {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 ( + - ); - })} -
- )} +
+ + {isMine ? t('myStory') : (group.user.displayName || group.user.userName || group.user.username || '?').split(' ')[0]} + + + ); + })} +
{/* Список чатов */}
diff --git a/client-web/src/modules/chats/presentation/components/ChatListItem.tsx b/client-web/src/modules/chats/presentation/components/ChatListItem.tsx index 3df8701..f591a02 100644 --- a/client-web/src/modules/chats/presentation/components/ChatListItem.tsx +++ b/client-web/src/modules/chats/presentation/components/ChatListItem.tsx @@ -148,7 +148,7 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) { {/* Аватар */}
{isFavorites ? ( -
+
bookmark
) : ( diff --git a/client-web/src/modules/chats/presentation/components/ChatView.tsx b/client-web/src/modules/chats/presentation/components/ChatView.tsx index f2c3b8e..40ea184 100644 --- a/client-web/src/modules/chats/presentation/components/ChatView.tsx +++ b/client-web/src/modules/chats/presentation/components/ChatView.tsx @@ -427,7 +427,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal initial={{ scale: 0.8, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }} - className="w-24 h-24 mb-6 rounded-full bg-[#1c1c1c] flex items-center justify-center shadow-[0_0_80px_rgba(48,150,229,0.15)] border border-white/5" + className="w-24 h-24 mb-6 rounded-[2rem] bg-[#1c1c1c] flex items-center justify-center shadow-[0_0_80px_rgba(48,150,229,0.15)] border border-white/5" > forum @@ -613,7 +613,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal >
{isFavorites ? ( -
+
bookmark
) : ( diff --git a/client-web/src/modules/chats/presentation/components/MessageBubble.tsx b/client-web/src/modules/chats/presentation/components/MessageBubble.tsx index 63c4ced..91b00c0 100644 --- a/client-web/src/modules/chats/presentation/components/MessageBubble.tsx +++ b/client-web/src/modules/chats/presentation/components/MessageBubble.tsx @@ -24,7 +24,7 @@ import { useAuthStore } from '../../../auth/application/authStore'; import { useChatStore } from '../../application/chatStore'; import { getSocket } from '../../../../core/infrastructure/socket'; import { useLang } from '../../../../core/infrastructure/i18n'; -import { extractWaveform, getMediaUrl, generateAvatarColor } from '../../../../core/utils/utils'; +import { extractWaveform, getMediaUrl, generateAvatarColor, getInitials } from '../../../../core/utils/utils'; import type { Message, MediaItem, Reaction, ChatMember } from '../../../../core/domain/types'; import ImageLightbox from '../../../../core/presentation/components/ui/ImageLightbox'; import LinkPreview from './LinkPreview'; @@ -313,7 +313,7 @@ function MessageBubble({ if (reactionGroups[r.emoji].avatars.length < 3) { reactionGroups[r.emoji].avatars.push({ url: r.user?.avatar, - initials: displayName[0].toUpperCase(), + initials: getInitials(displayName), colorClass: 'from-primary/80 to-primary-container' }); } @@ -398,10 +398,10 @@ function MessageBubble({ {showAvatar ? ( @@ -845,10 +845,10 @@ function MessageBubble({ {showAvatar ? ( diff --git a/client-web/src/modules/chats/presentation/components/NewChatModal.tsx b/client-web/src/modules/chats/presentation/components/NewChatModal.tsx index 0966fb1..92f6952 100644 --- a/client-web/src/modules/chats/presentation/components/NewChatModal.tsx +++ b/client-web/src/modules/chats/presentation/components/NewChatModal.tsx @@ -9,6 +9,8 @@ import { useAuthStore } from '../../../auth/application/authStore'; import { useLang } from '../../../../core/infrastructure/i18n'; import type { UserPresence, FriendWithId } from '../../../../core/domain/types'; +import { getInitials } from '../../../../core/utils/utils'; + interface NewChatModalProps { onClose: () => void; } @@ -55,6 +57,18 @@ export default function NewChatModal({ onClose }: NewChatModalProps) { const handleSelectUser = async (selectedUser: UserPresence) => { if (mode === 'personal') { try { + // Check if personal chat with this user already exists + const existingChat = useChatStore.getState().chats.find( + (c) => c.type === 'personal' && c.members.some((m) => m.user.id === selectedUser.id) + ); + + if (existingChat) { + setActiveChat(existingChat.id); + loadMessages(existingChat.id); + onClose(); + return; + } + const chat = await ChatApi.createPersonalChat(selectedUser.id); addChat(chat); import('../../../../core/infrastructure/socket').then(({ getSocket }) => getSocket()?.emit('join_chat', chat.id)); @@ -170,10 +184,10 @@ export default function NewChatModal({ onClose }: NewChatModalProps) { className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-knot-500/20 border border-knot-500/30" > {u.avatar ? ( - + ) : ( -
- {(u.displayName || u.userName || u.username || '?')[0]?.toUpperCase()} +
+ {getInitials(u.displayName || u.userName || u.username || '?')}
)} {u.displayName || u.userName || u.username || ''} @@ -279,7 +293,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) { ) : (
- {(u.displayName || u.userName || u.username || '?')[0]?.toUpperCase() || '?'} + {getInitials(u.displayName || u.userName || u.username || '?')}
)} {u.isOnline && ( @@ -331,7 +345,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) { ) : (
- {(u.displayName || u.userName || u.username || '?')[0]?.toUpperCase() || '?'} + {getInitials(u.displayName || u.userName || u.username || '?')}
)} {u.isOnline && ( diff --git a/client-web/src/modules/users/presentation/components/UserProfile.tsx b/client-web/src/modules/users/presentation/components/UserProfile.tsx index d55d407..536713c 100644 --- a/client-web/src/modules/users/presentation/components/UserProfile.tsx +++ b/client-web/src/modules/users/presentation/components/UserProfile.tsx @@ -15,6 +15,7 @@ import { httpClient } from '../../../../core/infrastructure/httpClient'; import { Loader2 } from 'lucide-react'; import ImageLightbox from '../../../../core/presentation/components/ui/ImageLightbox'; import type { FriendWithId, FriendRequest } from '../../../../core/domain/types'; +import { getInitials } from '../../../../core/utils/utils'; interface UserProfileProps { userId: string; @@ -225,12 +226,12 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf {/* Profile Card */}
-
+
{user.avatar ? ( ) : ( -
{user.displayName?.[0] || 'U'}
+
{getInitials(user.displayName)}
)}