- {/* Profile Picture */}
-
+

- {/* Username */}
- {currentData.username && (
-
-
+
+
+
+
+ {errors.display_name && (
+
{errors.display_name}
+ )}
+
+
+ {(currentData?.userId || currentData?.isOwnProfile) && !currentData.deleted && (
+
+
+
+ )}
+
+ {/* Admin Actions Section - Hide for deleted users */}
+ {!currentData.isOwnProfile && user.currentUser?.id === 1 && !currentData.deleted && (
+
+
Admin Actions
+
+
+ {currentData.suspended ? "Unsuspend Account" : "Suspend Account"}
+
+
+ Delete Account
+
+ {
+ setCurrentData({ ...currentData, verified });
+ }}
+ />
+
+
+ )}
+
+ {/* Verify button for non-admin owner */}
+ {!currentData.isOwnProfile && currentData.userId && user.currentUser?.id !== 1 && (
+
+ {
+ setCurrentData({ ...currentData, verified });
+ }}
/>
)}
- {/* Online Status */}
- {currentData?.userId && (
-
-
+ {/* Hide profile sections for deleted users */}
+ {!currentData.deleted && (
+
+
+
+ {currentData.bio !== undefined && (
+
+ )}
+
+ {currentData.memberSince && (
+
+ )}
+
+ {currentData.verified && (
+
+ )}
)}
-
- {/* Bio */}
- {currentData.bio !== undefined && (
-
- )}
-
- {/* Member Since */}
- {currentData.memberSince && (
-
-
-
- Участник с:
-
- {formatDate(currentData.memberSince)}
-
-
-
- )}
-
-
-
- {/* Save FAB */}
{currentData.isOwnProfile && (
)}
- {/* Hidden file input */}
-
-
,
- document.getElementById("root")!
+
);
}
diff --git a/frontend/src/pages/chat/ui/SuspensionDialog.tsx b/frontend/src/pages/chat/ui/SuspensionDialog.tsx
new file mode 100644
index 0000000..b9d1e22
--- /dev/null
+++ b/frontend/src/pages/chat/ui/SuspensionDialog.tsx
@@ -0,0 +1,42 @@
+import { StyledDialog } from "@/core/components/StyledDialog";
+
+interface SuspensionDialogProps {
+ reason: string;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export function SuspensionDialog({ reason, open, onOpenChange }: SuspensionDialogProps) {
+ return (
+
+
+
+
+
+
+
+
Аккаунт заблокирован
+
+ Ваш аккаунт был заблокирован за нарушение правил сообщества.
+ Вы не можете отправлять сообщения или взаимодействовать с другими пользователями.
+
+ {reason && reason !== "No reason provided" && (
+
+
Причина блокировки:
+
+ {reason}
+
+
+ )}
+
+ Если вы считаете, что блокировка была применена по ошибке,
+ обратитесь к администратору для рассмотрения вашего случая.
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/chat/ui/left/ChatHeader.tsx b/frontend/src/pages/chat/ui/left/ChatHeader.tsx
index fd112a5..ed69d72 100644
--- a/frontend/src/pages/chat/ui/left/ChatHeader.tsx
+++ b/frontend/src/pages/chat/ui/left/ChatHeader.tsx
@@ -13,7 +13,8 @@ export function ChatHeader() {
const handleProfileClick = () => {
setProfileDialog({
userId: user.currentUser?.id,
- username: profileData?.nickname || "Пользователь",
+ username: profileData?.username || "Пользователь",
+ display_name: profileData?.display_name || "Пользователь",
profilePicture: profileData?.profile_picture,
bio: profileData?.description,
memberSince: user.currentUser?.created_at,
diff --git a/frontend/src/pages/chat/ui/left/ChatTabs.tsx b/frontend/src/pages/chat/ui/left/ChatTabs.tsx
index 729cf55..5733364 100644
--- a/frontend/src/pages/chat/ui/left/ChatTabs.tsx
+++ b/frontend/src/pages/chat/ui/left/ChatTabs.tsx
@@ -1,21 +1,16 @@
import { useAppState, type ChatTabs } from "@/pages/chat/state";
import { UnifiedChatsList } from "./UnifiedChatsList";
-import type { FormEvent } from "react";
import type { Tabs } from "mdui/components/tabs";
export function ChatTabs() {
const { chat, setActiveTab } = useAppState();
- function handleChange(e: FormEvent
& CustomEvent<{ value: string }>) {
- setActiveTab(e.detail.value as ChatTabs);
- }
-
return (
+ onChange={(e) => setActiveTab((e.target as Tabs).value as ChatTabs)}>
Чаты
diff --git a/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx b/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx
index 4c1bcfa..58dea0b 100644
--- a/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx
+++ b/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx
@@ -4,10 +4,11 @@ import { useDM, type DMUser } from "@/pages/chat/hooks/useDM";
import { API_BASE_URL } from "@/core/config";
import { getAuthHeaders } from "@/core/api/authApi";
import { fetchUserPublicKey } from "@/core/api/dmApi";
+import { StatusBadge } from "@/core/components/StatusBadge";
import type { Message } from "@/core/types";
import { websocket } from "@/core/websocket";
import { onlineStatusManager } from "@/core/onlineStatusManager";
-import { OnlineIndicator } from "../right/OnlineIndicator";
+import { OnlineIndicator } from "@/pages/chat/ui/right/OnlineIndicator";
import defaultAvatar from "@/images/default-avatar.png";
interface PublicChat {
@@ -19,13 +20,16 @@ interface PublicChat {
interface DMConversation {
id: number;
+ userId: number;
username: string;
+ display_name: string;
profile_picture?: string;
online?: boolean;
type: "dm";
lastMessage?: string;
unreadCount: number;
publicKey?: string | null;
+ verified?: boolean;
}
type ChatItem = PublicChat | DMConversation;
@@ -83,7 +87,9 @@ export function UnifiedChatsList() {
const dmChatItems: ChatItem[] = dmUsers.map((user: DMUser) => ({
id: user.id,
+ userId: user.id, // Add userId field
username: user.username,
+ display_name: user.display_name,
profile_picture: user.profile_picture,
online: user.online,
type: "dm" as const,
@@ -172,13 +178,13 @@ export function UnifiedChatsList() {
};
}, [allChats]);
- const formatPublicChatMessage = (chatId: string): string => {
+ function formatPublicChatMessage(chatId: string): string {
const lastMessage = lastMessages[chatId];
if (!lastMessage) {
return "";
}
- const isCurrentUser = lastMessage.username === user.currentUser?.username;
+ const isCurrentUser = lastMessage.user_id === user.currentUser?.id;
const prefix = isCurrentUser ? "Вы: " : `${lastMessage.username}: `;
const maxContentLength = 50 - prefix.length;
@@ -187,14 +193,13 @@ export function UnifiedChatsList() {
: lastMessage.content;
return prefix + content;
- };
+ }
-
- const handlePublicChatClick = async (chatName: string) => {
+ async function handlePublicChatClick(chatName: string) {
await switchToPublicChat(chatName);
- };
+ }
- const handleDMClick = async (dmConversation: DMConversation) => {
+ async function handleDMClick(dmConversation: DMConversation) {
if (!dmConversation.publicKey) {
const authToken = useAppState.getState().user.authToken;
if (!authToken) return;
@@ -215,7 +220,7 @@ export function UnifiedChatsList() {
profilePicture: dmConversation.profile_picture,
online: dmConversation.online || false
});
- };
+ }
if (isLoadingUsers) {
return (
@@ -256,17 +261,25 @@ export function UnifiedChatsList() {
return (
handleDMClick(chat)}
style={{ cursor: "pointer" }}
>
+
+ {chat.display_name}
+
+
{chat.lastMessage || "Нет сообщений"}

handleUserClick(searchUser)}
style={{ cursor: "pointer" }}
>
+
+ {searchUser.username}
+
+

-
>(new Map());
const [loadedImages, updateLoadedImages] = useImmer>(new Set());
const [downloadingPaths, updateDownloadingPaths] = useImmer>(new Set());
@@ -164,26 +165,37 @@ export function Message({ message, isAuthor, onContextMenu, onReactionClick, isD
const imageRefs = useRef