From ff7a4b6bba18628293d38dee3f7877004ca025e8 Mon Sep 17 00:00:00 2001 From: Kirill Date: Thu, 21 May 2026 20:09:22 +0500 Subject: [PATCH] docs: avatar and display fixes design spec --- .../2026-05-21-avatar-display-fixes-design.md | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-21-avatar-display-fixes-design.md diff --git a/docs/superpowers/specs/2026-05-21-avatar-display-fixes-design.md b/docs/superpowers/specs/2026-05-21-avatar-display-fixes-design.md new file mode 100644 index 0000000..204848d --- /dev/null +++ b/docs/superpowers/specs/2026-05-21-avatar-display-fixes-design.md @@ -0,0 +1,174 @@ +# 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), рендер через `` + +**Файлы:** +- `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` — ссылка ``, иначе — просто текст `` + +**Файлы:** +- `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 ? : ` + +**Файлы:** +- `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 → │ +└───────────────────────────────────────────────────────┘ + +┌─ Order chat ──────────────────────────────────────────┐ +│ GET /api/orders/:id → { user: { avatar, ... } } │ +│ → OrderChat → ChatMessageBubble(avatar={})│ +│ 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 товаров