Фронт
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
|
||||
export interface UserBasic {
|
||||
id: string;
|
||||
username: string;
|
||||
userName: string;
|
||||
displayName: string;
|
||||
avatar: string | null;
|
||||
avatarUrl: string | null;
|
||||
// Fallbacks for compatibility
|
||||
username?: string;
|
||||
avatar?: string | null;
|
||||
}
|
||||
|
||||
export interface UserPresence extends UserBasic {
|
||||
@@ -48,14 +51,23 @@ export interface Reaction {
|
||||
id: string;
|
||||
emoji: string;
|
||||
userId: string;
|
||||
user: { id: string; username: string; displayName: string; avatar?: string | null };
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
userName?: string;
|
||||
displayName: string;
|
||||
avatar?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MessageSender {
|
||||
id: string;
|
||||
username: string;
|
||||
userName?: string;
|
||||
displayName: string;
|
||||
avatar?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
@@ -93,10 +105,11 @@ export interface Message {
|
||||
|
||||
export interface Chat {
|
||||
id: string;
|
||||
type: string;
|
||||
type: 'personal' | 'group' | 'channel' | 'favorites';
|
||||
name: string | null;
|
||||
description?: string | null;
|
||||
avatar: string | null;
|
||||
avatarUrl?: string | null;
|
||||
createdAt: string;
|
||||
members: ChatMember[];
|
||||
messages: Message[];
|
||||
@@ -152,8 +165,6 @@ export interface StoryGroup {
|
||||
hasUnviewed: boolean;
|
||||
}
|
||||
|
||||
// ─── Utility types ─────────────────────────────────────────────────
|
||||
|
||||
// ─── Friend types ──────────────────────────────────────────────────
|
||||
|
||||
export interface FriendRequest {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { deepNormalize } from '../utils/normalize';
|
||||
const API_BASE = '/api';
|
||||
|
||||
export class HttpClient {
|
||||
@@ -44,7 +45,8 @@ export class HttpClient {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
const jsonData = await response.json();
|
||||
return deepNormalize(jsonData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function Sidebar() {
|
||||
return chat.members.some(
|
||||
(m) =>
|
||||
m.user.id !== user?.id &&
|
||||
(m.user.username.toLowerCase().includes(q) ||
|
||||
((m.user.username || m.user.userName || '').toLowerCase().includes(q) ||
|
||||
m.user.displayName.toLowerCase().includes(q))
|
||||
);
|
||||
}).sort((a, b) => {
|
||||
@@ -163,7 +163,8 @@ export default function Sidebar() {
|
||||
</button>
|
||||
|
||||
{storyGroups.map((group, idx) => {
|
||||
const avatarUrl = group.user.avatar ? `${API_URL}${group.user.avatar}` : null;
|
||||
const avatar = group.user.avatarUrl || group.user.avatar;
|
||||
const avatarUrl = avatar ? `${API_URL}${avatar}` : null;
|
||||
const isMine = group.user.id === user?.id;
|
||||
return (
|
||||
<button
|
||||
@@ -187,7 +188,7 @@ 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).split(' ')[0]}
|
||||
{isMine ? t('myStory') : (group.user.displayName || group.user.userName || group.user.username || '').split(' ')[0]}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
67
client-web/src/core/utils/normalize.ts
Normal file
67
client-web/src/core/utils/normalize.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Глобальная нормализация данных пользователя.
|
||||
* Приводит данные от разных модулей (Profiles, Auth, Message) к единому виду.
|
||||
*
|
||||
* ВАЖНО: Мы не импортируем здесь типы из types.ts, чтобы избежать круговых зависимостей.
|
||||
*/
|
||||
export function normalizeUser(user: any): any {
|
||||
if (!user || typeof user !== 'object') return user;
|
||||
|
||||
const result = { ...user };
|
||||
|
||||
// 1. Приведение username (Auth -> userName, Profiles -> userName)
|
||||
if (!result.userName && result.username) {
|
||||
result.userName = result.username;
|
||||
}
|
||||
if (!result.username && result.userName) {
|
||||
result.username = result.userName;
|
||||
}
|
||||
|
||||
// 2. Приведение avatar (Auth -> avatar, Profiles -> avatarUrl)
|
||||
if (!result.avatarUrl && result.avatar) {
|
||||
result.avatarUrl = result.avatar;
|
||||
}
|
||||
if (!result.avatar && result.avatarUrl) {
|
||||
result.avatar = result.avatarUrl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Рекурсивная нормализация любого объекта/массива на наличие полей пользователя.
|
||||
*/
|
||||
export function deepNormalize(obj: any): any {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Если это File, Blob или FormData - не трогаем
|
||||
if (obj instanceof Blob || obj instanceof FormData) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(deepNormalize);
|
||||
}
|
||||
|
||||
// Создаем копию для мутации
|
||||
const result = { ...obj };
|
||||
|
||||
// Если это объект, похожий на пользователя (есть id и userName/username)
|
||||
if (result.id && (result.userName || result.username)) {
|
||||
const normalized = normalizeUser(result);
|
||||
// Продолжаем рекурсию по остальным полям (например, если у пользователя есть вложенные объекты)
|
||||
for (const key in normalized) {
|
||||
normalized[key] = deepNormalize(normalized[key]);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// Рекурсивно проходим по всем полям
|
||||
for (const key in result) {
|
||||
result[key] = deepNormalize(result[key]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user