Files
shop-server/docs/superpowers/specs/2026-05-21-avatar-display-fixes-design.md
T
2026-05-21 20:09:22 +05:00

175 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 товаров