Qadam Roadmap
проектdocs/Agents/specs/2026-03-24-mvp-spec-13-buyer-cabinet.md

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 = BUYER401/редирект на /login
Изоляция данныхПокупатель видит только свои лиды и отзывы403 при попытке доступа к чужим данным
Email — read-onlyEmail нельзя изменить через /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_idUUIDPK
account_idUUID FK→ Account (unique)
first_namestring2–50 символов
last_namestring2–50 символов
phonestring?+998XXXXXXXXX, nullable
date_of_birthDate?nullable
citystring?nullable, до 100 символов
avatar_urlstring?URL в CDN, nullable
updated_atDateTime

Используемые сущности (read-only)

СущностьЗадействованные атрибутыИсточник
Accountaccount_id, email, account_type, account_statusSpec 07
Leadlead_id, item_id, seller_id, buyer_id, lead_status, seller_note, created_atSpec 05
Itemitem_id, slug, title, cover_urlSpec 02
Sellerseller_id, seller_typeSpec 01
SchoolProfile / OnlineSchoolProfileorg_name, logo_url, phone, emailSpec 01
IndividualContributorProfilefirst_name, last_nameSpec 01
Reviewreview_id, item_id, rating, text, status, seller_reply, seller_reply_at, created_atSpec 14

Метрики /me (computed, не хранятся в БД)

МетрикаФормула
total_leadsCOUNT(Lead) WHERE buyer_id = :id
active_coursesCOUNT(Lead) WHERE buyer_id = :id AND lead_status IN (enrolled, attended, purchased)
total_reviewsCOUNT(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 пытается открыть /me403 — /me доступен только BUYER. Seller идёт на /seller
BuyerProfile ещё не создан (новый аккаунт)Создаётся при первом обращении с пустыми полями; first_name и last_name заполняются из данных регистрации
Покупатель удалил аккаунт (account_status = deleted)Токен инвалидируется; при обращении к /api/v1/me/* → 401
lead_id из другого buyer_id в URL403 FORBIDDEN — никогда 404 (чтобы не раскрывать существование)
avatar_url = broken URLonError → заглушка-инициалы
Параметр 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Исключено из MVPv1.0
История просмотров курсовИсключено из MVPv1.5 (Analytics)
Уведомления в кабинете (notification center)Исключено из MVPSpec 10 только Telegram
Удаление аккаунта покупателемИсключено из MVPТолько через поддержку
Смена emailИсключено из MVPТребует верификации — v1.0
Привязка социальных сетей (Google, Facebook)Исключено из MVPOAuth — v1.0
Мобильное приложение — адаптация /meTBDMobile-first дизайн требует отдельного UX-ревью
Экспорт данных покупателя (GDPR)Исключено из MVPv2.0
История изменений профиля (audit log)Исключено из MVPEventLog — v1.5
Пагинация отзывов: infinite scroll vs кнопкаTBDПока кнопка "Загрузить ещё"
Двухфакторная аутентификацияИсключено из MVPv1.0
Лимит на аватар: минимальные размеры (пиксели)TBDmin 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] в карточках