MVP Spec 13 — Buyer Personal Cabinet
Обновлён 1 апр. 2026 г., 12:41 · 0 комментариев
MVP Spec 13 — Buyer Personal Cabinet
Паспорт документа
- Статус документа: working spec
- Актуально на: 28 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: перед реализацией, при изменении domain rules или при смене source-of-truth по контракту
- Область применения: детальные feature-спеки для product backlog и реализации MVP/v1 направлений
- Связанные документы:
Version: MVP · Priority: P0 · Phase: B (Demand) Status: Draft v1 Sync note, 28 Mar 2026:
- live API prefix is
/api/v1/, not/api/;- текущий buyer cabinet API ограничен
GET/POST/PATCH /api/v1/me/profile,GET /api/v1/me/leads,GET /api/v1/me/reviews;/api/me/overview, avatar upload и change-password endpoints пока не являются текущим production contract.
1. Контекст и цель
Личный кабинет покупателя (/me/*) — это персональная зона пользователя на платформе Qadam. Здесь покупатель управляет своим профилем, отслеживает отправленные заявки (лиды) и просматривает написанные отзывы.
Цель модуля: дать покупателю единое место для контроля над своей активностью на платформе, обеспечить прозрачность статусов лидов и удобный доступ к своим отзывам.
Что не входит в этот модуль:
- Написание нового отзыва (инициируется со страницы курса) → Spec 14
- Подача заявки (лида) → Spec 05 (Lead Management)
- Авторизация и регистрация покупателя → Spec 07 (Auth)
- История платежей → Spec v1.5 (Payments)
- Уведомления → Spec 10
2. Роли пользователей
| Роль | Действия в этом модуле |
|---|---|
| Покупатель (авторизованный) | Просмотр дашборда, просмотр лидов, редактирование профиля, просмотр отзывов |
| Гость (неавторизованный) | Перенаправляется на /login при попытке доступа к /me/* |
3. Use Cases
UC-01: Покупатель открывает /me (Overview — дашборд)
Актор: Покупатель (авторизованный) Предусловие: Пользователь авторизован как BUYER Триггер: Нажимает "Мой кабинет" в хедере или переходит на /me напрямую
Полный поток:
[Точка входа]
→ Пользователь нажимает аватар / "Мой кабинет" в хедере
→ Открывается /me
─────────────────────────────────────────────────────────
НАВИГАЦИЯ КАБИНЕТА (левая панель / top tabs)
─────────────────────────────────────────────────────────
→ Пункты меню:
[Обзор] → /me (активна)
[Профиль] → /me/profile
[Мои заявки] → /me/leads
[Мои отзывы] → /me/reviews
─────────────────────────────────────────────────────────
РАЗДЕЛ — Приветствие
─────────────────────────────────────────────────────────
→ "Привет, {first_name}!" (берётся из BuyerProfile.first_name)
→ Если first_name не заполнен: "Привет!" (без имени)
─────────────────────────────────────────────────────────
РАЗДЕЛ — Метрики (Stats Cards)
─────────────────────────────────────────────────────────
→ Три карточки с числами:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Всего заявок │ │ Активных курсов│ │ Отзывов │
│ 12 │ │ 3 │ │ 5 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
"Всего заявок" = COUNT(Lead) где buyer_id = текущий
"Активных курсов" = COUNT(Lead) где lead_status IN (enrolled, attended, purchased)
"Отзывов" = COUNT(Review) где buyer_id = текущий AND status != rejected
─────────────────────────────────────────────────────────
РАЗДЕЛ — Последние заявки (Recent Leads)
─────────────────────────────────────────────────────────
→ Заголовок: "Последние заявки" + ссылка "Все заявки →"
→ Список последних 3 лидов:
Каждая запись:
- Обложка курса (cover_url) или заглушка
- Название курса (ссылка на /item/[slug])
- Название школы (ссылка на /sellers/[id])
- Статус лида: бейдж [Новая] / [В обработке] / [Зачислен] / [Отклонён]
- Дата подачи заявки
→ Статусы цветовые:
pending → серый
processing → жёлтый
enrolled / attended / purchased → зелёный
rejected → красный
→ Если лидов нет:
"Вы ещё не оставляли заявок."
Кнопка "Найти курс" → /catalog
─────────────────────────────────────────────────────────
РАЗДЕЛ — Последние отзывы (Recent Reviews)
─────────────────────────────────────────────────────────
→ Заголовок: "Мои отзывы" + ссылка "Все отзывы →"
→ Последние 2 отзыва:
- Название курса + звёзды рейтинга + дата
- Статус: [На проверке] / [Опубликован] / [Удалён]
→ Если отзывов нет — блок не отображается
UC-01 — Альтернативные потоки и обработка ошибок
1a. Ошибка загрузки метрик:
UI-реакция:
→ В карточке метрики вместо числа: "—"
→ Toast не показывается (тихий fallback)
1b. Долгая загрузка (> 1 сек):
UI-реакция:
→ Карточки метрик и список лидов показывают skeleton-loader
→ Spinner не используется
UC-02: Покупатель просматривает список заявок (/me/leads)
Актор: Покупатель (авторизованный) Предусловие: Пользователь авторизован Триггер: Нажимает "Мои заявки" в навигации кабинета
Полный поток:
→ Открывается /me/leads
─────────────────────────────────────────────────────────
ФИЛЬТРЫ
─────────────────────────────────────────────────────────
→ Фильтр по статусу (tabs или dropdown):
[Все] [Новые] [В обработке] [Зачислен] [Отклонён]
→ По умолчанию: "Все"
─────────────────────────────────────────────────────────
СПИСОК ЗАЯВОК
─────────────────────────────────────────────────────────
→ Каждая заявка — карточка:
- Обложка курса (cover_url) или серый placeholder
- Название курса (кликабельная ссылка → /item/[slug])
- Продавец: логотип + название школы (кликабельная ссылка → /sellers/[id])
- Дата подачи заявки (напр. "14 февраля 2026")
- Статус: цветной бейдж
- Кнопка "Подробнее" → открывает detail panel (UC-03)
→ Пагинация: 10 заявок на страницу
→ Сортировка: по дате DESC (newest first), нет возможности изменить
─────────────────────────────────────────────────────────
ПУСТОЕ СОСТОЯНИЕ
─────────────────────────────────────────────────────────
→ Иллюстрация
→ "У вас пока нет заявок"
→ Кнопка "Найти курс" → /catalog
UC-02 — Альтернативные потоки и обработка ошибок
2a. Ошибка загрузки списка заявок:
UI-реакция:
→ Вместо списка: "Не удалось загрузить заявки. Попробуйте снова."
→ Кнопка "Повторить"
2b. Фильтр выбран, но заявок с таким статусом нет:
UI-реакция:
→ Пустое состояние: "Нет заявок со статусом '{статус}'"
→ Ссылка "Показать все заявки"
UC-03: Покупатель просматривает детали заявки
Актор: Покупатель (авторизованный) Предусловие: Заявка существует и принадлежит этому покупателю Триггер: Нажимает "Подробнее" на карточке заявки в /me/leads
Полный поток:
→ Открывается боковая панель (drawer) или отдельная страница
с деталями заявки
─────────────────────────────────────────────────────────
СОДЕРЖИМОЕ ПАНЕЛИ ДЕТАЛЕЙ
─────────────────────────────────────────────────────────
→ Название курса (ссылка на /item/[slug])
→ Школа (ссылка на /sellers/[id])
→ Статус заявки (бейдж + расшифровка):
pending → "Заявка принята. Ожидайте контакта от школы."
processing → "Школа рассматривает вашу заявку."
enrolled → "Поздравляем! Вы зачислены на курс."
attended → "Вы посещаете курс."
purchased → "Курс оплачен."
rejected → "К сожалению, школа отклонила вашу заявку."
→ Дата подачи заявки
→ Контакты продавца (видны всегда после принятия заявки):
Телефон: +998 90 123-45-67 (кликабельный)
Email: school@example.com (кликабельный)
Адрес (если school_offline и display_publicly = true)
→ Комментарий покупателя (если был при подаче заявки)
→ Примечание от школы (lead.seller_note, если заполнено):
Блок с фоном: "Сообщение от школы:"
Текст примечания
─────────────────────────────────────────────────────────
CTA — НАПИСАТЬ ОТЗЫВ
─────────────────────────────────────────────────────────
→ Кнопка "Оставить отзыв" отображается если:
- lead_status IN (enrolled, attended, purchased)
- Покупатель ещё не написал отзыв на этот айтем (review отсутствует)
→ При нажатии: переход на /item/[slug]#review-form
(якорная ссылка на форму отзыва на странице курса)
→ Если отзыв уже написан: кнопка заменяется на "Отзыв написан ✓"
(ссылка на /me/reviews)
UC-03 — Альтернативные потоки и обработка ошибок
3a. Попытка открыть чужую заявку (перебор lead_id):
API-реакция:
→ GET /api/v1/me/leads/:lead_id → 403 FORBIDDEN
UI-реакция:
→ Панель не открывается
→ Toast (красный): "Нет доступа к этой заявке."
3b. Заявка удалена или более не существует:
API-реакция:
→ 404 LEAD_NOT_FOUND
UI-реакция:
→ Toast (красный): "Заявка не найдена."
→ Список обновляется
UC-04: Покупатель редактирует профиль (/me/profile)
Актор: Покупатель (авторизованный) Предусловие: Пользователь авторизован Триггер: Нажимает "Профиль" в навигации кабинета
Полный поток:
→ Открывается /me/profile
─────────────────────────────────────────────────────────
ФОРМА ПРОФИЛЯ
─────────────────────────────────────────────────────────
→ Аватар:
Круглое фото (avatar_url) или заглушка-инициалы
Кнопка "Изменить фото" (загрузка файла)
→ Поля формы (предзаполнены текущими данными):
Имя * (2–50 символов)
Фамилия * (2–50 символов)
Телефон (+998XXXXXXXXX, необязательно)
Email (read-only — отображается, но не редактируется)
Дата рождения (необязательно, date picker)
Город (необязательно, free text или select)
→ Кнопка "Сохранить изменения"
─────────────────────────────────────────────────────────
СЕКЦИЯ — СМЕНА ПАРОЛЯ
─────────────────────────────────────────────────────────
→ Отдельный блок ниже формы профиля:
"Изменить пароль"
Текущий пароль *
Новый пароль * (min 8 символов)
Подтвердите пароль *
Кнопка "Изменить пароль"
→ Валидация при потере фокуса (on blur)
→ После сохранения: Toast "Профиль обновлён ✓"
UC-04 — Альтернативные потоки и обработка ошибок
4a. Обязательное поле пустое при сохранении:
UI-реакция:
→ Поле: красная обводка + ⚠
→ Под полем: "Это поле обязательно для заполнения"
→ Форма не отправляется
4b. Загрузка аватара — файл > 5 МБ:
UI-реакция:
→ Под полем фото: ⚠ "Файл слишком большой. Максимум 5 МБ."
→ Файл не принимается, аватар не меняется
4c. Загрузка аватара — неверный формат:
UI-реакция:
→ Под полем фото: ⚠ "Поддерживаются форматы JPG, PNG, WebP."
→ Файл не принимается
4d. Неверный текущий пароль:
API-реакция:
→ 400 WRONG_CURRENT_PASSWORD
UI-реакция:
→ Поле "Текущий пароль": красная обводка + ⚠
→ Под полем: "Неверный текущий пароль."
4e. Новый пароль не совпадает с подтверждением:
UI-реакция (client-side, до отправки):
→ Поле "Подтвердите пароль": красная обводка + ⚠
→ Под полем: "Пароли не совпадают."
→ Форма не отправляется
4f. Новый пароль слишком слабый:
API-реакция (или client-side):
→ 400 PASSWORD_TOO_WEAK
UI-реакция:
→ Поле "Новый пароль": красная обводка + ⚠
→ Под полем: "Пароль должен содержать минимум 8 символов."
4g. Технический сбой при сохранении:
UI-реакция:
→ Toast (красный): "Не удалось сохранить изменения. Попробуйте ещё раз."
→ Форма не закрывается, данные не сбрасываются
UC-05: Покупатель просматривает свои отзывы (/me/reviews)
Актор: Покупатель (авторизованный) Предусловие: Пользователь авторизован Триггер: Нажимает "Мои отзывы" в навигации кабинета
Полный поток:
→ Открывается /me/reviews
─────────────────────────────────────────────────────────
СПИСОК ОТЗЫВОВ
─────────────────────────────────────────────────────────
→ Каждый отзыв — карточка:
- Обложка курса (cover_url) или серый placeholder
- Название курса (ссылка на /item/[slug])
- Школа (ссылка на /sellers/[id])
- Звёзды рейтинга (1–5)
- Дата написания
- Текст отзыва
- Статус отзыва (видит только покупатель):
pending → бейдж жёлтый "На проверке"
published → бейдж зелёный "Опубликован"
rejected → бейдж красный "Удалён"
pending_moderation→ бейдж жёлтый "На повторной проверке"
- Если status = rejected:
Под карточкой серый блок:
"Ваш отзыв был удалён администратором."
- Если status = pending_moderation:
Под карточкой серый блок:
"Ваш отзыв временно скрыт — поступила жалоба.
Он будет проверен администратором."
- Ответ продавца (если seller_reply заполнен и status = published):
Блок с фоном: "{org_name} отвечает:"
Текст ответа + дата ответа
─────────────────────────────────────────────────────────
ПУСТОЕ СОСТОЯНИЕ
─────────────────────────────────────────────────────────
→ "Вы ещё не оставляли отзывов."
→ "Оставить отзыв можно на странице курса после подачи заявки."
→ Кнопка "Перейти в каталог" → /catalog
UC-05 — Альтернативные потоки и обработка ошибок
5a. Ошибка загрузки списка отзывов:
UI-реакция:
→ "Не удалось загрузить отзывы. Попробуйте снова."
→ Кнопка "Повторить"
UC-06: Неавторизованный пользователь пытается открыть /me
Актор: Гость (неавторизованный) Триггер: Вводит /me/* в адресную строку или переходит по ссылке
Полный поток:
→ Middleware проверяет наличие сессии / JWT токена
→ Токен отсутствует или просрочен
→ Пользователь перенаправляется на /login
→ В URL добавляется параметр return:
/login?return=/me
/login?return=/me/leads
/login?return=/me/reviews
→ После успешного входа:
Автоматический редирект на исходный URL (из параметра return)
Если return параметр не задан или невалидный → редирект на /me
─────────────────────────────────────────────────────────
НА СТРАНИЦЕ /login
─────────────────────────────────────────────────────────
→ Сообщение не показывается (тихий редирект, не ошибка)
→ Стандартная форма входа
4. Бизнес-правила и валидации
| Правило | Описание | Ошибка / Поведение |
|---|---|---|
| Доступ только для BUYER | /me/* доступно только аккаунтам с account_type = BUYER | 401/редирект на /login |
| Изоляция данных | Покупатель видит только свои лиды и отзывы | 403 при попытке доступа к чужим данным |
| Email — read-only | Email нельзя изменить через /me/profile | Поле отображается как disabled |
| Имя обязательно | first_name и last_name обязательны в профиле | "Это поле обязательно для заполнения" |
| Имя: длина | 2–50 символов | "Имя: от 2 до 50 символов" |
| Телефон: формат | +998XXXXXXXXX если заполнен | "Введите номер в формате +998XXXXXXXXX" |
| Аватар: размер | max 5 МБ | ⚠ "Файл слишком большой. Максимум 5 МБ." |
| Аватар: формат | JPG, PNG, WebP | ⚠ "Поддерживаются форматы JPG, PNG, WebP." |
| Метрика "Активных курсов" | lead_status IN (enrolled, attended, purchased) | |
| Метрика "Отзывов" | status != rejected | Не считаются удалённые |
| Пароль: длина | min 8 символов | "Пароль должен содержать минимум 8 символов." |
| Смена пароля: верификация | Требует ввода текущего пароля | 400 WRONG_CURRENT_PASSWORD |
| Отображение отзывов | Все статусы (кроме rejected) видны покупателю | Rejected отображается с пометкой "Удалён" |
| Кнопка "Оставить отзыв" | Только если lead_status IN (enrolled, attended, purchased) AND нет review | Скрыта если условие не выполнено |
5. Модель данных
Этот модуль не создаёт новых сущностей в большинстве случаев. Единственное расширение — BuyerProfile.
BuyerProfile (профиль покупателя)
| Атрибут | Тип | Описание |
|---|---|---|
| buyer_profile_id | UUID | PK |
| account_id | UUID FK | → Account (unique) |
| first_name | string | 2–50 символов |
| last_name | string | 2–50 символов |
| phone | string? | +998XXXXXXXXX, nullable |
| date_of_birth | Date? | nullable |
| city | string? | nullable, до 100 символов |
| avatar_url | string? | URL в CDN, nullable |
| updated_at | DateTime |
Используемые сущности (read-only)
| Сущность | Задействованные атрибуты | Источник |
|---|---|---|
| Account | account_id, email, account_type, account_status | Spec 07 |
| Lead | lead_id, item_id, seller_id, buyer_id, lead_status, seller_note, created_at | Spec 05 |
| Item | item_id, slug, title, cover_url | Spec 02 |
| Seller | seller_id, seller_type | Spec 01 |
| SchoolProfile / OnlineSchoolProfile | org_name, logo_url, phone, email | Spec 01 |
| IndividualContributorProfile | first_name, last_name | Spec 01 |
| Review | review_id, item_id, rating, text, status, seller_reply, seller_reply_at, created_at | Spec 14 |
Метрики /me (computed, не хранятся в БД)
| Метрика | Формула |
|---|---|
| total_leads | COUNT(Lead) WHERE buyer_id = :id |
| active_courses | COUNT(Lead) WHERE buyer_id = :id AND lead_status IN (enrolled, attended, purchased) |
| total_reviews | COUNT(Review) WHERE buyer_id = :id AND status != rejected |
6. Технические контракты
6.1 Prisma Schema (добавления)
// Профиль покупателя определён в Spec 08. Этот модуль использует существующие таблицы:
// Parent { parent_id, buyer_id, first_name, last_name, phone, email?, avatar_url?, ... }
// Student { student_id, buyer_id, first_name, last_name, date_of_birth?, class_number?, ... }
// НЕТ единого model BuyerProfile — используем Parent или Student в зависимости от buyer_type.
// email читается из Account (не из Parent/Student), изменение email исключено из MVP.
6.2 TypeScript DTOs
// ─── Профиль покупателя ───────────────────────────────────────────────────
export interface BuyerProfileDto {
buyer_profile_id: string
first_name: string
last_name: string
phone: string | null
email: string // из Account — read-only
date_of_birth: string | null // ISO date "YYYY-MM-DD"
city: string | null
avatar_url: string | null
}
export class UpdateBuyerProfileDto {
@IsString() @MinLength(2) @MaxLength(50)
first_name: string
@IsString() @MinLength(2) @MaxLength(50)
last_name: string
@IsOptional() @Matches(/^\+998\d{9}$/)
phone?: string
@IsOptional() @IsDateString()
date_of_birth?: string
@IsOptional() @IsString() @MaxLength(100)
city?: string
@IsOptional() @IsUrl()
avatar_url?: string
}
export class ChangeBuyerPasswordDto {
@IsString()
current_password: string
@IsString() @MinLength(8)
new_password: string
@IsString()
confirm_password: string
}
// ─── Дашборд (обзор) ─────────────────────────────────────────────────────
export interface BuyerOverviewDto {
greeting_name: string | null // first_name или null
stats: BuyerStatsDto
recent_leads: BuyerLeadListItemDto[] // последние 3
recent_reviews: BuyerReviewListItemDto[] // последние 2
}
export interface BuyerStatsDto {
total_leads: number
active_courses: number
total_reviews: number
}
// ─── Заявки покупателя ────────────────────────────────────────────────────
export interface BuyerLeadListItemDto {
lead_id: string
item_id: string
item_title: string
item_slug: string
item_cover_url: string | null
seller_id: string
seller_name: string // org_name или first_name + last_name
seller_logo_url: string | null
lead_status: LeadStatus
created_at: string // ISO 8601
}
export interface BuyerLeadDetailDto extends BuyerLeadListItemDto {
seller_phone: string | null
seller_email: string | null
seller_address: string | null // full_address если display_publicly = true
buyer_comment: string | null
seller_note: string | null
can_review: boolean // true если eligible и нет review
review_id: string | null // если уже есть отзыв
}
// Canonical LeadStatus — см. knowledge-base.md "Canonical Enums"
export type LeadStatus = 'new' | 'contacted' | 'enrolled' | 'attended' | 'no_show' | 'purchased' | 'not_purchased' | 'rejected'
// ─── Отзывы покупателя ────────────────────────────────────────────────────
export interface BuyerReviewListItemDto {
review_id: string
item_id: string
item_title: string
item_slug: string
item_cover_url: string | null
seller_id: string
seller_name: string
rating: number
text: string
status: ReviewStatus
created_at: string
seller_reply: string | null
seller_reply_at: string | null
}
export type ReviewStatus = 'pending' | 'published' | 'rejected' | 'pending_moderation'
6.3 API Endpoints
────────────────────────────────────────────────────────────────
BUYER CABINET: ОБЗОР
────────────────────────────────────────────────────────────────
GET /api/v1/me/overview
Auth: Bearer (buyer)
→ 200: BuyerOverviewDto
→ 401: { error: 'UNAUTHORIZED' }
────────────────────────────────────────────────────────────────
BUYER CABINET: ПРОФИЛЬ
────────────────────────────────────────────────────────────────
GET /api/v1/me/profile
Auth: Bearer (buyer)
→ 200: BuyerProfileDto
→ 401: { error: 'UNAUTHORIZED' }
PATCH /api/v1/me/profile
Auth: Bearer (buyer)
Body: UpdateBuyerProfileDto
→ 200: BuyerProfileDto
→ 422: { errors: ValidationError[] }
POST /api/v1/me/profile/avatar
Auth: Bearer (buyer)
Body: multipart/form-data { file: File }
→ 200: { avatar_url: string }
→ 400: { error: 'FILE_TOO_LARGE' | 'INVALID_FILE_TYPE', message: string }
POST /api/v1/me/change-password
Auth: Bearer (buyer)
Body: ChangeBuyerPasswordDto
→ 200: { success: true }
→ 400: { error: 'WRONG_CURRENT_PASSWORD' | 'PASSWORDS_DO_NOT_MATCH' | 'PASSWORD_TOO_WEAK', message: string }
────────────────────────────────────────────────────────────────
BUYER CABINET: ЗАЯВКИ
────────────────────────────────────────────────────────────────
GET /api/v1/me/leads
Auth: Bearer (buyer)
Query: ?status=pending&page=1&limit=10
→ 200: { leads: BuyerLeadListItemDto[], total: number, has_more: boolean }
→ 401: { error: 'UNAUTHORIZED' }
GET /api/v1/me/leads/:lead_id
Auth: Bearer (buyer)
→ 200: BuyerLeadDetailDto
→ 403: { error: 'FORBIDDEN' }
→ 404: { error: 'LEAD_NOT_FOUND' }
────────────────────────────────────────────────────────────────
BUYER CABINET: ОТЗЫВЫ
────────────────────────────────────────────────────────────────
GET /api/v1/me/reviews
Auth: Bearer (buyer)
Query: ?page=1&limit=10
→ 200: { reviews: BuyerReviewListItemDto[], total: number, has_more: boolean }
→ 401: { error: 'UNAUTHORIZED' }
7. Edge Cases
| Сценарий | Поведение |
|---|---|
| Гость переходит на /me/leads | Редирект на /login?return=/me/leads |
| Продавец типа Seller пытается открыть /me | 403 — /me доступен только BUYER. Seller идёт на /seller |
| BuyerProfile ещё не создан (новый аккаунт) | Создаётся при первом обращении с пустыми полями; first_name и last_name заполняются из данных регистрации |
| Покупатель удалил аккаунт (account_status = deleted) | Токен инвалидируется; при обращении к /api/v1/me/* → 401 |
| lead_id из другого buyer_id в URL | 403 FORBIDDEN — никогда 404 (чтобы не раскрывать существование) |
| avatar_url = broken URL | onError → заглушка-инициалы |
| Параметр return содержит внешний домен | Игнорируется; редирект только на /me после входа |
| Метрики при 0 лидах / 0 отзывов | Числа = 0, не null; отображается "0" |
| Загрузка аватара: файл не изображение (напр. PDF) | 400 INVALID_FILE_TYPE |
| Покупатель пытается изменить email | Поле disabled, PATCH игнорирует email даже если передан |
| Отзыв в статусе pending_moderation | Виден в /me/reviews с пометкой "На повторной проверке"; скрыт с публичных страниц |
| Пустой return параметр в /login?return= | Редирект на /me после входа |
8. TBD / Сознательно опущено
| Тема | Статус | Примечание |
|---|---|---|
| Избранные курсы / Wishlist | Исключено из MVP | v1.0 |
| История просмотров курсов | Исключено из MVP | v1.5 (Analytics) |
| Уведомления в кабинете (notification center) | Исключено из MVP | Spec 10 только Telegram |
| Удаление аккаунта покупателем | Исключено из MVP | Только через поддержку |
| Смена email | Исключено из MVP | Требует верификации — v1.0 |
| Привязка социальных сетей (Google, Facebook) | Исключено из MVP | OAuth — v1.0 |
| Мобильное приложение — адаптация /me | TBD | Mobile-first дизайн требует отдельного UX-ревью |
| Экспорт данных покупателя (GDPR) | Исключено из MVP | v2.0 |
| История изменений профиля (audit log) | Исключено из MVP | EventLog — v1.5 |
| Пагинация отзывов: infinite scroll vs кнопка | TBD | Пока кнопка "Загрузить ещё" |
| Двухфакторная аутентификация | Исключено из MVP | v1.0 |
| Лимит на аватар: минимальные размеры (пиксели) | TBD | min 100×100px? Уточнить с дизайном |
9. Зависимости
| Модуль | Связь |
|---|---|
| Spec 07 (Auth) | Account, авторизация, JWT, смена пароля |
| Spec 05 (Lead Management) | Lead сущность, lead_status, seller_note |
| Spec 01 (Seller Profile) | Данные продавца для отображения в карточках лидов |
| Spec 02 (Item Management) | Item данные (title, slug, cover_url) |
| Spec 14 (Reviews) | Review сущность, статусы, seller_reply |
| Spec 12 (Public Seller Profile) | Ссылки на /sellers/[id] в карточках |