Предметная областьМодулиАудит маркетплейса

Аудит маркетплейса PlanForge

Актуально на: 2026-05-26

Дата анализа: 15 апреля 2026
Проанализированы все файлы: страницы, API, компоненты, модели данных, уведомления, навигация


1. Текущая архитектура (что есть)

Модель данных

МодельНазначение
PlanListingЛистинг (projectId, sellerId, purchaseType, price, status, previewFields)
OrderЗаказ/покупка (listingId, buyerId, purchaseType, price, resultProjectId)
ListingReviewРешение ревьюера (decision: APPROVE/REJECT, comment)
PlanAccessКонтроль доступа к проекту (userId, projectId, accessLevel)
NotificationУведомления (type, title, message, link, isRead)

Страницы и навигация

СтраницаНавигацияНазначение
/marketplaceHeader (все пользователи)Каталог одобренных листингов
/marketplace/[id]Не ссылается из UIОтдельная страница листинга (мёртвый код)
/projects/[id]/publishИконка на карточке проектаФорма публикации плана
/my-listingsHeader (все пользователи)Управление своими листингами
/my-purchasesHeader (все пользователи)История покупок
/reviewsHeader (REVIEWER/ADMIN)Единая очередь ревью (публикации + планы)
/projects/[id]?back=/reviewsСсылка из очередиРевью прямо в плане (панель ревью)
/admin/marketplaceТаб в админке (ADMIN)Статистика

API endpoints (13 шт.)

  • Публичные: listings (GET), filters (GET), listing detail (GET)
  • Покупка: purchase (POST)
  • Продавец: my-listings (GET), listing details (GET), archive (POST), resubmit (POST), reviews history (GET)
  • Ревьюер: очередь /api/reviews/plans (GET), заявка /api/plans/[id]/review (GET/PATCH — решение по публикации), пометки /api/plans/[id]/review/notes
  • Покупатель: my-purchases (GET)

2. Детальный разбор механик: что работает и что нет

2.1. Публикация листинга

Поток: Страница проектов → иконка «Опубликовать» → /projects/[id]/publish → выбор типа/цены/превью-полей → POST /api/plans/[id]/listings → статус PENDING_REVIEW

Что работает:

  • Выбор типа продажи (Копия / Эксклюзив)
  • Ввод цены (валидация > 0 и <= 10 000 000)
  • Выбор превью-полей (checkbox-и: название, компания, отрасль, регион, сценарий, срок)
  • Проверка что у проекта нет активного листинга (race-safe через транзакцию)
  • Проверка что пользователь — владелец проекта

Что НЕ работает / отсутствует:

  • Нет описания листинга — продавец не может добавить свободный текст, рассказывающий о плане
  • Нет предпросмотра — продавец не видит как его листинг будет выглядеть до отправки
  • Нет проверки «пустого» плана ✅ — серверная + клиентская валидация: нельзя опубликовать план без продуктов, расходов и сотрудников
  • Нет предупреждения для EXCLUSIVE_TRANSFER — при эксклюзиве продавец потеряет план, но на странице публикации нет яркого предупреждения о необратимости
  • После публикации редирект на /projects ✅ — заменён на /my-listings
  • Нет валидации минимальной цены на клиенте — можно ввести 0.01 RUB

2.2. Ревью листинга ✅ ПЕРЕВЕДЕНО НА ДВИЖОК РЕВЬЮ ПЛАНА

Поток: публикация (или правка описания/обложки/галереи одобренного листинга, или resubmit после отказа) открывает заявку PlanReviewRequest с kind = LISTING и ссылкой на листинг → ревьюер видит её в общей очереди /reviews → открывает сам проект в воркспейсе (read-only через canReviewProject) → оставляет пометки с привязкой к месту в плане → автор закрывает их → ревьюер завершает заявку решением APPROVE / REJECT, что и публикует листинг или возвращает его на доработку.

Что работает:

  • Единая очередь: заявки на публикацию и планы «просто на разбор» в одном списке с фильтром по поводу
  • Ревьюер работает в реальном плане: расчёты, графики, все разделы — а не readonly-выгрузка
  • Пометки по разделам с якорем на элемент, снимком среза и диффом «что изменил автор»
  • Коммерческая карточка листинга (цена, тип, тираж, обложка, галерея, previewFields) — в панели ревью
  • Одобрение с незакрытыми пометками требует подтверждения (force)
  • Отклонение требует причину — она уходит в rejectionComment и в уведомление автору
  • История решений остаётся в ListingReview, её читают «Мои листинги»
  • Отзыв заявки автором снимает листинг с публикации (архив); архивация листинга закрывает заявку

Что осталось:

  1. Нет чеклиста / критериев ревью — ревьюер не имеет гайдлайнов, на что обращать внимание

  2. PlanAccess REVIEW по-прежнему не используется — доступ ревьюера даёт canReviewProject (активная заявка + глобальная роль), а уровень REVIEW в PlanAccess и API /api/plans/[id]/reviewers живут отдельно и не привязаны к UI

  3. Нет промежуточного статуса «нужны правки» — вместо него пометки: заявка остаётся IN_PROGRESS, пока автор их не закроет


2.3. Копирование проекта при покупке (COPY)

Поток: Покупатель нажимает «Купить» → подтверждение → POST /api/purchase/[listingId] → loadWorkspaceStateFromDb → создание нового проекта → saveWorkspaceStateToDb → ответ с projectId

Что работает:

  • Атомарная транзакция для создания проекта, PlanAccess и Order
  • Дубль-проверка внутри транзакции (перепроверка статуса)
  • Защита от повторной покупки одной и той же копии
  • Создание workspace для покупателя если его нет
  • Имя проекта с суффиксом «(копия)»
  • Откат при неудачном сохранении state

Что потенциально проблемно:

  1. saveWorkspaceStateToDb выполняется ВНЕ транзакции — это осознанный выбор (save слишком тяжёлый для транзакции), но откат неатомарен:

    // Откатываем в единой транзакции, чтобы не оставлять сирот
    await prisma.$transaction([...]).catch(() => {});

    Если откат тоже упадёт — останутся Order и Project-сироты.

  2. Не копируются настройки проектапроверено, claim снят. loadWorkspaceStateFromDb загружает все данные (ProjectInfo, ProjectSettings, Products, Personnel, Expenses, Calendar), saveWorkspaceStateToDb их полностью сохраняет. Копирование данных — полное.

  3. Нет уведомления покупателю ✅ — добавлено уведомление типа PLAN_PURCHASED для покупателя при COPY и EXCLUSIVE_TRANSFER.

  4. Скопированный проект получает статус DRAFT — это ок, но нет объяснения для покупателя что это значит.

  5. Нет «происхождения» — в скопированном проекте нигде не указано, что он был куплен на маркетплейсе, от какого продавца.


2.4. Эксклюзивная передача (EXCLUSIVE_TRANSFER)

Что работает:

  • FOR UPDATE lock на строке листинга (race condition protection)
  • Перенос проекта в workspace покупателя
  • Seller получает NO_ACCESS
  • Buyer получает OWNER
  • Листинг переходит в статус SOLD
  • Уведомление продавцу

Что проблемно:

  1. Необратимость без предупреждения ✅ — добавлен заметный amber-баннер с иконкой ⚠️ на странице публикации при выборе EXCLUSIVE_TRANSFER
  2. Продавец видит «Продан» но не знает кому — в /my-listings нет информации о покупателе
  3. Нет проверки workspace переносаложный claim, удалён. Вся операция (PlanAccess, workspace transfer, статус SOLD, Order) выполняется внутри одной $transaction. Если любой шаг упадёт — откатится всё. Это сделано правильно.

2.5. Маркетплейс (каталог)

Что работает:

  • Поиск (debounced)
  • 6 фильтров: тип покупки, отрасль, регион, сценарий, мин/макс цена
  • 4 варианта сортировки
  • Серверная пагинация (12 на страницу)
  • Индикатор «Ваш план» / «Куплено» на карточках
  • Модалка с деталями по клику
  • Сайдбар фильтров с мобильным toggle

Что НЕ работает / отсутствует:

  1. previewFields игнорируются при показе — в API listings/[id]/route.ts (строка 55-70) превью формируется из всех полей ProjectInfo (title, company, industry, region, variant, scenario, durationMonths, startDate), а не из тех полей, которые продавец выбрал в previewFields. Продавец выбирает что показывать, но API это полностью игнорирует и показывает всё. previewFields используется только как fallback если ProjectInfo пуст:

    const previewData = Object.keys(richPreview).length > 0 ? richPreview : listing.previewFields;
  2. Счётчик «Состав плана» виден только владельцу/покупателю — другие пользователи не видят даже количество продуктов/сотрудников/расходов. Это снижает привлекательность карточки.

  3. Нет информации о дате публикации на карточке — createdAt не отображается.

  4. Модалка vs отдельная страница рассинхронизированы:

    • ListingModal использует поля isOwner, isPurchased, purchasedProjectId
    • ListingDetailPage (/marketplace/[id]) использует accessLevel: "full" | "preview" — поле которого нет в API ответе
    • Отдельная страница листинга не показывает кнопки для владельца/покупателя (нет isOwner/isPurchased в типе)
    • Это мёртвый код: страница существует, но никуда не ведёт из основного UI
  5. Нет пустого состояния с CTA — «Опубликуйте свой план или зайдите позже» но нет кнопки «Опубликовать»


2.6. Мои листинги (продавец)

Что работает:

  • Таблица с колонками: название, статус, тип, цена, продажи, доход
  • Статусные бейджи (PENDING_REVIEW, APPROVED, REJECTED, SOLD, ARCHIVED)
  • Причина отклонения при REJECTED
  • Архивирование (PENDING/APPROVED → ARCHIVED)
  • Повторная отправка (REJECTED → PENDING_REVIEW) с изменением цены/типа/превью
  • Модалка детальной информации (продукты, персонал, расходы, инвестиции)
  • История ревью (inline)
  • Пагинация

Что НЕ работает / отсутствует:

  1. Нет редактирования активного листинга ✅ — добавлено изменение цены APPROVED листинга (без повторного ревью, с настраиваемым кулдауном)
  2. Нет ссылки «Посмотреть в маркетплейсе» — для APPROVED листингов нет быстрого перехода на его страницу/модалку в маркетплейсе
  3. Нет информации о покупателях — продавец видит «3 продажи», но не знает кто купил
  4. Нет фильтрации по статусу — если листингов много, нельзя отфильтровать только активные/отклонённые
  5. Модалка деталей не закрывается при навигации — query-параметр listing остаётся в URL

2.7. Мои покупки (покупатель)

Что работает:

  • Таблица: название, продавец, тип, цена, дата, ссылка на проект
  • Пагинация
  • ✅ Поиск по названию плана и продавцу (debounced, серверный)
  • ✅ Сортировка: по дате (новые/старые), по цене (↑/↓)
  • ✅ Суммарная статистика: количество покупок и общая потраченная сумма
  • ✅ Filter-aware empty state: «Ничего не найдено» + сброс фильтров

Что НЕ работает / отсутствует:

  1. Скудный UI — это самая простая из всех страниц, просто таблица без фильтров, поиска, сортировки
  2. Нет статуса проекта — покупатель не видит состояние купленного проекта
  3. Нет возможности оставить отзыв — нет CTA для обратной связи
  4. Нет группировки — все покупки в одном плоском списке
  5. Нет суммарной статистики — общая потраченная сумма, количество покупок

2.8. Админ-панель маркетплейса

Что работает:

  • 7 счётчиков: всего листингов, на ревью, активных, продано, всего продаж, сумма продаж, пользователей
  • Auto-refresh каждые 5 секунд

Что НЕ работает / отсутствует:

  1. Polling каждые 5 секунд — слишком агрессивно, лишняя нагрузка на БД
  2. Нет списка листингов — админ видит только числа, не может просмотреть конкретные листинги, модерировать, удалять
  3. Нет графиков/трендов — сколько продаж за неделю/месяц, динамика
  4. Нет управления пользователями — нельзя заблокировать продавца, изменить роль
  5. Нет логов действий — кто когда что одобрил/отклонил/купил

2.9. Уведомления

Что работает:

  • Колокольчик в header
  • Polling каждые 30 сек
  • «Прочитать все»
  • Относительное время (только что, N мин назад, N ч назад)
  • Ссылки в уведомлениях

Что НЕ работает / отсутствует:

  1. 4 типа уведомлений реализованы:

    • LISTING_APPROVED → продавцу (ссылка на /my-listings) ✅ ссылка исправлена
    • LISTING_REJECTED → продавцу (ссылка на /my-listings)
    • PLAN_SOLD → продавцу (ссылка на /my-listings)
    • PLAN_PURCHASED → покупателю (ссылка на /my-purchases) ✅ добавлено
  2. Не реализованы уведомления:

    • Покупателю при покупке
    • Ревьюерам о новых листингах
    • Продавцу о просмотрах
    • При изменении цены
  3. Нет удаления отдельных уведомлений — только массовое «прочитать все»

  4. Нет push-уведомлений — только polling

  5. Ссылка в уведомлении LISTING_APPROVED ведёт на мёртвую страницу /marketplace/${listingId} — страница существует но не интегрирована в UI


3. Критические баги и рассинхронизации

3.1. previewFields полностью сломаны ✅ ИСПРАВЛЕНО

Продавец на странице /projects/[id]/publish выбирает checkbox-ами какие поля показывать (title, company, industry…). Эти настройки сохраняются в PlanListing.previewFields как JSON { title: true, industry: true }. Но API в /api/marketplace/listings/[id]/route.ts полностью игнорирует previewFields и всегда показывает ВСЕ заполненные поля из ProjectInfo. Теперь richPreview и project.info фильтруются по ключам из previewFields.

3.2. Отдельная страница листинга /marketplace/[id] рассинхронизирована ✅ ИСПРАВЛЕНО

Страница удалена как мёртвый код. Ссылка в уведомлении LISTING_APPROVED заменена на /my-listings.

3.3. Покупка COPY: дубль-проверка слишком строгая

const existingOrder = await tx.order.findFirst({
  where: { listingId: listing.id, buyerId, purchaseType: "COPY" },
});
if (existingOrder) throw new Error("DUPLICATE_COPY_PURCHASE");

Это значит что покупатель не может купить копию одного плана дважды. Для типа COPY это может быть нежелательно — зачем ограничивать? Если план обновился, покупатель может захотеть новую копию.


4. Создание листинга: нет нормальной точки входа

Единственный способ создать листинг — найти маленькую иконку IconUpload на карточке проекта в /projects. Всё. Больше нигде нет CTA «Продать» или «Создать листинг»:

  • /marketplace (пустое состояние) ✅ — добавлена кнопка «Опубликовать план» → /projects
  • /my-listings (пустое состояние) ✅ — добавлена кнопка «Опубликовать план» → /projects
  • Header — нет кнопки «Продать» / «Создать листинг»
  • Проект (workspace) — нет кнопки «Опубликовать» внутри самого редактора проекта

Пользователь, который хочет продать план, должен сам догадаться вернуться на страницу со списком проектов и найти мелкую иконку. Это критическая проблема discoverability — основной revenue-путь маркетплейса спрятан.

Что нужно:

  • CTA «Продать план» на /marketplace и /my-listings (хотя бы в пустых состояниях)
  • Кнопка «Опубликовать в маркетплейсе» внутри редактора проекта
  • Возможно, кнопка «Продать» в header рядом с навигацией

5. Что отсутствует в UI полностью

ФичаСтатусКомментарий
Описание листинга❌ НетНигде нет textarea для описания
Превью содержимого для покупателя❌ НетПокупатель видит только мета-информацию
Редактирование листинга✅ ЧастичноИзменение цены APPROVED листинга (с кулдауном), тип/описание — только через resubmit
Профиль продавца❌ НетТолько имя/email
Отзывы покупателей❌ НетНет модели, нет UI
Избранное❌ НетНет wishlist
Сравнение✅ ЕстьМодалка сравнения 2-3 планов (карточки/таблица), подсветка лучших значений
Категории/теги❌ НетТолько поля из ProjectInfo
Скелетоны при загрузке✅ Естьanimate-pulse skeleton карточки и строки таблиц
Ревьюер → проект (read-only)❌ НетТолько статические таблицы
Анимации переходов✅ ЕстьCSS transitions на модалках и stagger на карточках
Mobile-оптимизация таблиц❌ НетТаблицы на /my-listings и /my-purchases без responsive
Sorting в /my-listings✅ ЕстьСортировка по дате/цене
Фильтры в /my-listings✅ ЕстьФильтрация по статусу
Поиск в /my-purchases✅ ЕстьПоиск по названию/продавцу + сортировка
Платёжная система❌ НетДемо-режим

6. Безопасность и надёжность

5.1. Email продавца виден всем ✅ ИСПРАВЛЕНО

seller.email отдаётся в API и отображается если нет имени. Убрано из публичных API, fallback на «Автор».

5.2. Нет rate limiting

Нет ограничения на API-запросы. Можно спамить покупками и т.д.

5.3. Нет кеширования фильтров

/api/marketplace/filters пересчитывает distinct-значения на каждый запрос.

5.4. Неатомарный откат копирования

saveWorkspaceStateToDb вне транзакции, откат может не сработать.

5.5. Нет audit log

Кто и когда что сделал — не логируется (кроме ListingReview).


7. Задачи по категориям сложности

Задачи из GitHub issues (#82-#90) помечены тегом GH#XX. Issue #82 (сайдбар с фильтрами) — уже реализован (marketplace-sidebar.tsx), можно закрыть.

🟢 Быстрые победы (1-2 часа каждая)

Минимальные изменения, максимальный эффект. Баги, однострочные фиксы, мелкие UI-доработки.

#ЗадачаЧто делатьФайлы
1Фикс: previewFields игнорируютсяrichPreview фильтруется по ключам из listing.previewFields; каталог также фильтрует project.infoapi/marketplace/listings/[id]/route.ts, api/marketplace/listings/route.ts
2Фикс: ссылка LISTING_APPROVED ведёт на мёртвую страницуСсылка заменена на /my-listingsapi/reviews/[listingId]/approve/route.ts
3Фикс: /marketplace/[id] рассинхронизированаСтраница удалена (мёртвый код)
4CTA на пустых состоянияхДобавлены кнопки «Опубликовать план» (ссылка на /projects) в empty state на /marketplace и /my-listingsmarketplace-client.tsx, my-listings-client.tsx
5Уведомление покупателю при покупкеДобавлены createNotification типа PLAN_PURCHASED для buyer в handleCopyPurchase и handleExclusiveTransfer. Добавлено значение PLAN_PURCHASED в enum NotificationTypeapi/purchase/[listingId]/route.ts, schema.prisma
6Скрыть email продавцаseller.email убран из публичных API, fallback на «Автор»api/marketplace/listings/route.ts, api/marketplace/listings/[id]/route.ts, listing-modal.tsx, marketplace-client.tsx
7Редирект после публикации → /my-listingsЗаменён router.push("/projects") на router.push("/my-listings")projects/[projectId]/publish/page.tsx
8Предупреждение EXCLUSIVE при публикацииДобавлен заметный amber-баннер с предупреждением о необратимости при выборе «Эксклюзивная передача»projects/[projectId]/publish/page.tsx
9GH#90 Галочка пройденного ревьюДобавлен бейдж «Проверено» с иконкой IconRosetteDiscountCheck на каждой карточке листинга в маркетплейсеmarketplace-client.tsx (ListingCard)

🟡 Средние задачи (3-6 часов каждая)

Новые UI-компоненты, расширение API, изменения в нескольких файлах.

#ЗадачаЧто делатьФайлы
10Описание листингаПоле description в PlanListing, textarea на publish/resubmit, отображение в модалке и карточке, поиск по описаниюschema, publish, resubmit, listing-modal, marketplace-client
11Фильтры и сортировка в «Мои листинги»Фильтр по статусу (5 значений), сортировка по дате/цене. Select-компоненты в UImy-listings-client.tsx, api/my-listings/route.ts
12Улучшение «Мои покупки»Поиск по названию/продавцу (debounced), сортировка (дата/цена), суммарная статистика (потрачено, кол-во покупок), filter-aware empty statemy-purchases-client.tsx, api/my-purchases/route.ts
13Уведомления ревьюерамТип LISTING_SUBMITTED, уведомление всем REVIEWER/ADMIN при publish и resubmitapi/plans/[id]/listings/route.ts, api/.../resubmit/route.ts
14Ссылка «Посмотреть в маркетплейсе» для продавцаКнопка «В маркетплейсе» для APPROVED листингов открывает ListingModal прямо из /my-listingsmy-listings-client.tsx
15Редактирование цены APPROVED листингаPATCH endpoint для изменения цены без повторного ревью, кулдаун (настраивается в админке), модалка в «Мои листинги»api/marketplace/listings/[id]/price/route.ts, api/admin/settings/route.ts, my-listings-client.tsx, admin/marketplace/page.tsx
16Скелетоны загрузкиSkeleton-карточки (animate-pulse) на маркетплейсе, skeleton-строки в таблицах «Мои листинги» и «Мои покупки»marketplace-client.tsx, my-listings-client.tsx, my-purchases-client.tsx
17Проверка «пустого» плана при публикацииСерверная проверка (products + expenses + employees = 0 → 400), клиентский warning-баннер с блокировкой кнопкиapi/plans/[id]/listings/route.ts, api/plans/[id]/route.ts, publish/page.tsx
18Анимации модалок и переходовCSS transitions: fade+slide модалок (listing-modal, project-detail-modal), stagger fadeSlideUp карточек в каталогеlisting-modal.tsx, project-detail-modal.tsx, marketplace-client.tsx
19GH#85 Пины карточек от проектаПоле isPinned в PlanListing, toggle в админке, закреплённые отображаются вверху каталога с бейджемschema, API listings, marketplace-client, admin
20GH#86 Маркетинговые цифры магазина (как на VC)Счётчики на главной: «Планов», «Авторов», «Покупок», «Средняя цена». API endpoint /api/marketplace/stats с агрегатамиapi/marketplace/stats/route.ts, marketplace-client.tsx
21GH#87 Картинки для карточек листинговЗагрузка обложки (cover image) при публикации, отображение на карточке и в модалке. Хранение в S3/Supabase Storageschema (imageUrl), upload API, publish page, ListingCard, listing-modal
22GH#88 Метки авторов и инфлюенсеровБейджи на карточке/профиле: «Эксперт», «Топ-автор», «Инфлюенсер». Поле badges/tags в User или отдельная модельschema, API, ListingCard, seller info
23GH#83 Сравнение бизнес-плановМодалка сравнения 2-3 планов (карточки рядом / таблица), подсветка лучших значений, плавающая панель выбора, API /api/marketplace/comparecompare-modal.tsx, marketplace-client.tsx, api/marketplace/compare/route.ts

🔴 Сложные задачи (1-3 дня каждая)

Новые модели данных, полноценные фичи, архитектурные изменения.

#ЗадачаЧто делатьОбъём
24Ревьюер → read-only доступ к проектуПри поступлении листинга на ревью: создавать PlanAccess REVIEW, пускать ревьюера в workspace в режиме чтения. Нужен read-only mode в редакторе проектаМиграция логики, UI read-only mode, permissions
25Система отзывов и рейтинговНовая модель BuyerReview (rating, comment), API CRUD, средний рейтинг на карточке, страница отзывов, рейтинг продавцаSchema, API (3 routes), UI (2-3 компонента)
26Профиль продавцаСтраница /sellers/[id] с аватаром, описанием, статистикой, списком листингов, рейтингомНовая страница, API route, компоненты
27Интеграция платёжной системыЮKassa/Stripe: создание платежа, webhook, статусы (pending/paid/failed/refunded), Order только после оплатыAPI routes, webhook handler, UI checkout flow, env config
28Избранное / WishlistНовая модель Favorite, API toggle, иконка сердечка на карточках, страница «Избранное»Schema, API, UI компоненты, новая страница
29Превью содержимого для покупателяПоказать частичные данные плана (например, названия продуктов без цен, категории расходов без сумм) до покупкиAPI логика маскирования, UI компоненты
30Возвраты и диспутыМодель Dispute, поток запроса возврата, UI для покупателя и админа, статусыSchema, API (4-5 routes), UI (2 страницы)
31GH#84 Перенести с VC формат карточекПолный редизайн карточек листингов: обложка, описание, теги, автор с аватаром, социальные метрики — стиль карточек как на vc.ruListingCard, listing-modal, marketplace-client, возможно новые компоненты
32GH#89 MVP партнерской программыРеферальные ссылки, модель Referral (referrerId, referredId, orderId, commission), дашборд партнёра, начисление комиссии при продажеSchema, API (3-4 routes), UI (новая страница дашборда), бизнес-логика в purchase

8. Что сделано хорошо

  • Атомарные транзакции — покупка и передача прав через $transaction
  • FOR UPDATE lock — защита от двойной покупки эксклюзива
  • Debounce цены — фильтры по цене не спамят API
  • Ролевая модель — USER / REVIEWER / ADMIN в навигации и API
  • Уведомления с polling — рабочий notification bell с 30-сек обновлением
  • Аналитика — трекинг всех ключевых событий воронки
  • Модалка деталей для продавца — полная информация по проекту с аккордеонами
  • Серверная пагинация — с настраиваемым pageSize и подсчётом total
  • Мобильный toggle фильтров — сайдбар скрывается на маленьких экранах
  • Повторная отправка — отклонённый листинг можно переподать с новыми параметрами