# 2026-05-21 — Avatar & Display Name Fixes

## Overview

8 замечаний по отображению аватаров, имён, ссылок и stock-статусов в админке и на клиенте.

## Approach

Локальные изолированные правки (Подход 1). Каждый пункт правится в своём контексте без переиспользования общих компонентов с `/me` — минимизирует риск регрессии.

---

## 1. Admin settings page (`/admin/settings`)

**Проблема:** Админ не может настроить displayName/avatar. Страница `/me/settings` существует, но админ редиректится с `/me` на `/admin`.

**Решение:**
- Новая FSD-страница `client/src/pages/admin-settings/`
- Пункт «Настройки» в сайдбаре `AdminLayoutPage` (после «Уведомления»)
- Форма: редактирование `displayName`, выбор/генерация аватара (DiceBear, 16 стилей), загрузка своего аватара. Копирует UI с `/me/settings` (SettingsPage), но как отдельный компонент, не шаринг.
- API: `GET /api/admin/profile` и `PATCH /api/admin/profile` (новый роут в `server/src/routes/api/`)
- Роут защищён `verifyAdmin`, работает с полями: `displayName`, `avatar`, `avatarType`, `avatarStyle`
- После сохранения — инвалидация `$user` стора на клиенте, чтобы хедер подхватил новый аватар

**Файлы:**
- `client/src/pages/admin-settings/ui/AdminSettingsPage.tsx` (новый)
- `client/src/pages/admin-layout/ui/AdminLayoutPage.tsx` (добавить пункт меню)
- `server/src/routes/api/admin-profile.js` (новый)
- `server/src/index.js` (зарегистрировать роут)

---

## 2. Admin avatar in header

**Проблема:** В хедере админ видит только кнопку «Выход», без аватара.

**Решение:**
- В `AppHeader` для админа: `IconButton` с `UserAvatar` + выпадающее меню с пунктами «Настройки» (`/admin/settings`) и «Выход»
- Аватар из `AuthUser.avatar/avatarType/avatarStyle`, при отсутствии — DiceBear fallback
- Компонент по аналогии с `UserMenu`, но упрощённый: только 2 пункта, без профиля покупателя. Можно сделать как `AdminUserMenu` в `features/user/user-menu/` или прямо в `AppHeader`

**Файлы:**
- `client/src/app/layout/AppHeader.tsx` (заменить кнопку «Выход» на меню с аватаром)

---

## 3. Avatar column in admin users table

**Проблема:** В таблице пользователей (`/admin/users`) нет колонки с аватарами.

**Решение:**
- `AdminUser` тип (`entities/user/model/types.ts`): добавить `avatar`, `avatarType`, `avatarStyle` (опциональные)
- Серверный `GET /api/admin/users`: добавить эти поля в SELECT
- `AdminUsersPage`: колонка «Аватар» первой (перед email), рендер через `<UserAvatar size={28} />`

**Файлы:**
- `client/src/entities/user/model/types.ts`
- `client/src/pages/admin-users/ui/AdminUsersPage.tsx`
- `server/src/routes/api/admin-users.js`

---

## 4. Avatars in order messages

**Проблема:** `ChatMessageBubble` показывает только текст «Админ»/«Вы»/«Пользователь», без аватаров.

**Решение:**
- `ChatMessageBubble`: добавить опциональный проп `avatar?: ReactNode` — рендерится слева от сообщения для `authorType='admin'`, справа для `'user'`
- `OrderChat` (пользователь): для админских сообщений — DiceBear по `'admin'` seed, для своих — аватар из `AuthUser`
- `OrderDetailContent` (админ): для пользователя — аватар из `order.user.avatar/avatarType/avatarStyle`, для админа — из `AuthUser`
- API `GET /api/orders/:id` и `GET /api/admin/orders/:id`: добавить `user { avatar, avatarType, avatarStyle }` в ответ
- Клиентский тип заказа: добавить эти поля в `user`

**Файлы:**
- `client/src/shared/ui/ChatMessageBubble.tsx`
- `client/src/features/order-chat/ui/OrderChat.tsx`
- `client/src/features/order-detail/ui/OrderDetailContent.tsx`
- `server/src/routes/user-orders.js` (GET /:id)
- `server/src/routes/api/admin-orders.js` (GET /:id)
- Типы заказа на клиенте

---

## 5. Actual user avatars in reviews

**Проблема:** В отзывах всегда генерируется DiceBear по строке `authorDisplay`, а не используется реальный аватар пользователя.

**Решение:**
- API `public-reviews`: добавить `authorAvatar`, `authorAvatarType`, `authorAvatarStyle` в ответ (из `user.avatar/avatarType/avatarStyle`)
- Тип `PublicProductReviewItem` и `PublicReviewFeedItem`: добавить эти поля
- `ReviewsBlock` и `ProductReviewsList`: передавать реальные значения в `UserAvatar` вместо `null`

**Файлы:**
- `server/src/routes/api/public-reviews.js`
- `client/src/entities/review/api/reviews-api.ts` (типы)
- `client/src/widgets/reviews-block/ui/ReviewsBlock.tsx`
- `client/src/features/product-review/ui/ProductReviewsList.tsx`
- `server/src/routes/api/admin-reviews.js` (тоже может использовать)

---

## 6. Product link in reviews only if published

**Проблема:** В `ReviewsBlock` ссылка на товар показывается всегда, даже если товар скрыт из каталога.

**Решение:**
- API `public-reviews`: добавить объект `product: { id, title, published, slug }` в каждый элемент фида
- Тип `PublicReviewFeedItem`: обновить поле с `productId`/`productTitle` на `product: { id, title, published, slug }`
- `ReviewsBlock`: если `product.published === true` — ссылка `<RouterLink>`, иначе — просто текст `<Typography>`

**Файлы:**
- `server/src/routes/api/public-reviews.js`
- `client/src/entities/review/api/reviews-api.ts`
- `client/src/widgets/reviews-block/ui/ReviewsBlock.tsx`

---

## 7. "Out of stock" chip visibility in catalog

**Проблема:** Чип «Нет в наличии» существует в DOM, но визуально не виден в каталоге.

**Решение:**
- Проверить `z-index` чипа в `ProductCard` — поднять выше (например `zIndex: 2`), чтобы не перекрывался `CardMedia` или другими элементами
- Предположительно проблема в том, что чип рендерится до изображения в DOM, и изображение перекрывает его по z-order

**Файлы:**
- `client/src/entities/product/ui/ProductCard.tsx`

---

## 8. Person icon for unauthenticated users

**Проблема:** До авторизации в хедере нет иконки пользователя.

**Решение:**
- В `AppHeader`: когда `user === null` и `!loading`, показывать `IconButton` с `PersonIcon`, ведущую на `/auth`
- Сейчас `UserMenu` не рендерится без `user` — добавить условие `user ? <UserMenu ...> : <IconButton href="/auth"><PersonIcon /></IconButton>`

**Файлы:**
- `client/src/app/layout/AppHeader.tsx`

---

## Data flow summary

```
┌─ Admin settings ─────────────────────────────────────┐
│  PATCH /api/admin/profile → DB → invalidate $user     │
│  → AppHeader reads $user.avatar → UserAvatar          │
└───────────────────────────────────────────────────────┘

┌─ Admin users table ───────────────────────────────────┐
│  GET /api/admin/users → { ..., avatar, avatarType,    │
│  avatarStyle } → AdminUsersPage → <UserAvatar />      │
└───────────────────────────────────────────────────────┘

┌─ Order chat ──────────────────────────────────────────┐
│  GET /api/orders/:id → { user: { avatar, ... } }      │
│  → OrderChat → ChatMessageBubble(avatar={<UserAvatar/>})│
│  Admin avatar: from AuthUser store                    │
└───────────────────────────────────────────────────────┘

┌─ Reviews ─────────────────────────────────────────────┐
│  GET /api/public-reviews → { authorAvatar, ...,       │
│  product: { published, ... } }                        │
│  → ReviewsBlock/ProductReviewsList → UserAvatar + link│
└───────────────────────────────────────────────────────┘
```

## Testing

- **Client unit tests:** Проверить рендер аватаров в `ProductReviewsList`, `ReviewsBlock`, `AdminUsersPage`, `ChatMessageBubble`, `AppHeader` для разных состояний (авторизован/неавторизован/админ)
- **Server tests:** Проверить новые поля в ответах API
- **Manual:** Проверить видимость чипа «Нет в наличии», отображение ссылки в отзывах для published/unpublished товаров
