# Находка Платформа для поиска потерянных вещей и питомцев. SPA на React 18 с JWT-аутентификацией и автоматическим обновлением токена, чатом на WebSocket (STOMP/SockJS), картами Яндекса, загрузкой изображений, админ-панелью рекламы и модерации объявлений, i18n (ru/en) и светлой/тёмной темой. --- ## Содержание 1. [Быстрый старт](#1-быстрый-старт) 2. [Переменные окружения](#2-переменные-окружения) 3. [Стек и зависимости](#3-стек-и-зависимости) 4. [Скрипты](#4-скрипты) 5. [Структура проекта](#5-структура-проекта) 6. [Маршруты](#6-маршруты) 7. [Ключевые компоненты](#7-ключевые-компоненты) 8. [Контексты](#8-контексты) 9. [Хуки](#9-хуки) 10. [API-эндпоинты](#10-api-эндпоинты) 11. [WebSocket](#11-websocket) 12. [Админские эндпоинты](#12-админские-эндпоинты) 13. [Стили и темы](#13-стили-и-темы) 14. [Основные сценарии](#14-основные-сценарии) 15. [Известные проблемы и предупреждения](#15-известные-проблемы-и-предупреждения) --- ## 1. Быстрый старт Требуется Node.js 18+ (проверено на Node 24). ```bash npm install # зависимости (см. раздел о .npmrc) npm start # dev-сервер на http://localhost:3000 npm run build # production-сборка в build/ ``` Фронтенд ожидает работающий бэкенд (по умолчанию `http://localhost:8080`). Если нужно обращаться к другому адресу — скопируйте `.env.example` в `.env` и задайте `REACT_APP_API_URL`. --- ## 2. Переменные окружения Файл `.env` **не** коммитится (см. `.gitignore`), шаблон — `.env.example`. | Переменная | Обязательна | По умолчанию | Описание | |------------|-------------|--------------|----------| | `REACT_APP_API_URL` | да | `http://localhost:8080` | Базовый URL бэкенда | | `REACT_APP_YANDEX_GEOCODER_KEY` | для карты | — | Яндекс.Геокодер: обратное геокодирование координат → адрес | | `REACT_APP_YANDEX_MAPS_KEY` | для карты | — | Публичный ключ Яндекс.Карт, подставляется в `public/index.html` | | `REACT_APP_SUPPORT_EMAIL` | нет | есть значение по умолчанию | Почта поддержки в модалке «Поддержка» | > **Безопасность.** Ключи Яндекса — публичные, но попадание реального `.env` в git > означает утечку в историю коммитов. Если файл уже коммитился, перевыпустите ключи: > добавление `.env` в `.gitignore` не удаляет его из истории. --- ## 3. Стек и зависимости | Пакет | Назначение | |-------|-----------| | `react` / `react-dom` 18 | UI | | `react-router-dom` 7 | Роутинг (`BrowserRouter`) | | `@stomp/stompjs` + `sockjs-client` | WebSocket (STOMP) для чата и непрочитанных | | `framer-motion` | Анимации модалок и переходов | | `react-hook-form` + `@hookform/resolvers` + `zod` 4 | Формы и их валидация | | `i18next` + `react-i18next` | Локализация ru/en | | `@emoji-mart/react` + `@emoji-mart/data` + `emoji-mart` | Эмодзи-пикер в чате | | `react-scripts` 5.0.1 | Сборка/дев-сервер (CRA) | Карты Яндекса подключаются напрямую через глобальный `window.ymaps` (см. `src/utils/ymapsApi.js`), без обёртки — она ждёт готовности API с таймаутом и отдаёт ошибку вместо вечного спиннера. Dev-зависимости: `typescript` 5.9, `@types/react` 18, `@types/react-dom`, `@types/node`, `@testing-library/*`. ### `.npmrc` и конфликт peer-зависимостей В репозитории лежит `.npmrc` с `legacy-peer-deps=true`. Причина: `react-scripts@5.0.1` объявляет `typescript` **опциональным** peer-диапазоном `^3.2.1 || ^4`, а проекту нужен TypeScript 5 (на TS 4 не собираются `@types/react` 18.3 и `zod` 4). Конфликт формальный: CRA типы не проверяет — их вырезает babel, а проверка запускается отдельно через `npm run typecheck`. Побочный эффект `legacy-peer-deps`: npm не ставит peer-зависимости автоматически, поэтому `emoji-mart` объявлен в `dependencies` явно (его требует `@emoji-mart/react`). --- ## 4. Скрипты | Скрипт | Что делает | |--------|-----------| | `npm start` | Dev-сервер с hot reload | | `npm run build` | Production-сборка в `build/` | | `npm test` | Jest в watch-режиме | | `npm run test:ci` | Тесты один раз (`--watchAll=false`) — для CI | | `npm run lint` | ESLint по `src`, `--max-warnings=0` | | `npm run typecheck` | `tsc --noEmit` по `tsconfig.json` | Перед коммитом стоит прогнать все четыре проверки: `lint`, `typecheck`, `test:ci`, `build`. --- ## 5. Структура проекта ``` src/ ├── index.js # Точка входа ├── App.js # Провайдеры + маршруты ├── i18n.js # Инициализация i18next, persist языка ├── legacy.d.ts # Типы ассетов (*.css, *.png, ...) │ ├── api/ │ ├── apiClient.jsx # apiFetch: Authorization + refresh токена по 401 │ ├── adminAdsApi.js # Кампании, реклама, фуллскрин-реклама │ ├── adminContentApi.js # Админские объявления: список, статус, удаление │ └── adsApi.js # Фуллскрин-реклама, пресigned-выгрузка видео │ ├── config/ │ ├── authConstants.js # API_URL │ ├── feedConstants.js # Пункты меню, категории, пустая форма │ └── landingConstants.js # Контент лендинга │ ├── context/ │ ├── AuthContext.jsx # user, token, isAuthenticated, isAdmin, login/logout │ ├── ThemeContext.jsx # Светлая/тёмная тема │ └── UnreadContext.jsx # Счётчики непрочитанных чатов │ ├── stores/ │ ├── activeChatStore.js # Активный чат │ └── chatPostStore.js # Кэш «объявление ↔ чат» │ ├── hooks/ # см. раздел 9 │ ├── utils/ │ ├── adSession.js # ID рекламной сессии (устойчив к запрету localStorage) │ ├── campaignStatus.js # Статусы кампаний │ ├── chatDisplay.jsx # Данные для списка чатов │ ├── compressImage.js # Сжатие JPEG до 1200px │ ├── formatPhone.js # Маска +7 (___) ___-__-__ │ ├── validatePostForm.js # Валидация формы объявления │ ├── withAds.js # Рекламная обёртка │ └── ymapsApi.js # Ожидание window.ymaps с таймаутом │ ├── pages/ │ ├── NakhodkaLanding.jsx # Лендинг «/» │ ├── MainFeed.tsx # Лента объявлений — главная страница │ ├── MapModal.jsx, MapView.jsx # Карта и полноэкранный режим │ ├── ChatListPage.jsx, ChatPage.jsx │ ├── ReviewsPage.jsx │ ├── CreateUpdatePost.jsx # Создание/редактирование (в т.ч. adminMode) │ ├── PostOnFeed.jsx # Карточка объявления │ ├── RulesModal.jsx, SupportModal.jsx │ └── AdminAdsPage.jsx # Админ-панель │ ├── components/ │ ├── feed/ FeedTopBar.tsx, FeedContent, FeedSearchBar, PullToRefresh, RewardPopup │ ├── nav/ BottomNav.jsx, Sidebar.jsx │ ├── layout/ AppTopBar.jsx │ ├── chat/ ChatHeader, ChatInputBar, ChatListItem, MessageBubble │ ├── reviews/ RatingSummary, ReviewCard, ReviewForm, StarRating │ ├── profile/ Profile, ProfileAvatar, ProfileFormFields, ProfileModal, profileValidation │ ├── admin/ PostsTab, AdvertisingCreate/EditModal, CampaignCreate/EditModal, ... │ ├── landing/ Hero, Navbar, StepsSection, RadarBeacon, FinalCta, Footer, Icon │ ├── icons/ NavIcons + Calendar/Close/Globe/Menu/Moon/Phone/Pin/Sun/Trash │ └── … AuthModal, Toast, MapView, PostDetailModal, ImageViewer, AdCard, AdSlot, ... │ ├── styles/ │ ├── index.css # Точка входа CSS │ ├── base.css # Переменные, сброс, keyframes │ ├── layout.css, sidebar.css, card.css, modal.css, ui.css │ └── chat.css, reviews.css, rules.css, admin.css, NakhodkaLanding.css │ └── locales/ ├── ru.json └── en.json ``` --- ## 6. Маршруты | Путь | Компонент | Доступ | |------|-----------|--------| | `/` | `NakhodkaLanding` | публичный лендинг | | `/feed` | `MainFeed` | лента (главная) | | `/reviews` | `ReviewsPage` | отзывы о площадке | | `/chats` | `ChatListPage` | список чатов | | `/chats/:chatId` | `ChatPage` | чат | | `/admin/ads` | `AdminAdsPage` | только `isAdmin` (иначе редирект на `/feed`) | | `*` | `NakhodkaLanding` | 404-fallback | Провайдеры: `ThemeProvider` → `AuthProvider` → `BrowserRouter`. --- ## 7. Ключевые компоненты ### `MainFeed.tsx` Главная страница. Собирает топбар, поиск, фильтры, ленту, сайдбары, карту, модалки (объявление, профиль, вход, правила, поддержка, просмотр фото). Делегирует загрузку и мутации хукам `useFeedPosts`, `useCityFeed`, `useSearchPosts`, `useMyPosts`, `useFeedActions`, `useModals`. Ошибки удаления/отметки «найдено» показываются через `Toast`. ### `PostOnFeed.jsx` Карточка объявления: фото, бейджи категории/типа/статуса, заголовок, описание, адрес, дата, телефон, кнопка карты и «Написать». Для владельца — редактирование, удаление, отметка «найдено». ### `CreateUpdatePost.jsx` Модалка создания/редактирования. Переключатель Потеря/Находка, заголовок, описание, фото (сжатие на клиенте), категория, карта Яндекса с меткой и обратным геокодированием, адрес, телефон с маской, чекбокс правил. Валидация — `validatePostForm`. С пропом `adminMode` шлёт `PUT /api/v1/admin/posts/{id}`. ### `FeedContent.jsx` / `FeedTopBar.tsx` / `PullToRefresh.jsx` Лента, топбар со спрятанной панелью на скролле, pull-to-refresh с корректным cleanup класса активности. ### `MapView.jsx` Полноэкранная карта. Ждёт готовности API Яндекса, показывает плашку ошибки, если он не загрузился. Первая загрузка маркеров идёт по фактическим границам карты, а не по событию `load` (которого у `ymaps.Map` в 2.1 нет). ### `AdminAdsPage.jsx` + `components/admin/PostsTab.jsx` Админ-панель: вкладки «Рекламные кампании» и «Все объявления». Кампании, реклама, загрузка видео (presigned → S3 → confirm), фуллскрин-реклама. `PostsTab` — список всех объявлений с поиском, фильтрами, пагинацией, правкой, закрытием/возвратом в ленту и удалением. --- ## 8. Контексты ### `AuthContext` ```js { user, // object | null token, // string | null — JWT isAuthenticated, // !!token isAdmin, // user.role === "ADMIN" — только скрытие UI, не защита login(user, token), // пишет в localStorage + state logout(), // чистит localStorage + state updateUser(userData), // обновляет пользователя } ``` Хранится в `localStorage`: `user` (JSON), `token`, `refreshToken`. Хук: `useAuthContext()`. ### `ThemeContext` Светлая/тёмная тема; выбор сохраняется. ### `UnreadContext` Счётчики непрочитанных по чатам; сбрасываются при смене пользователя, данные приходят из WS-подписки `/topic/user/{userId}/unread`. --- ## 9. Хуки ### Авторизация и API | Хук | Назначение | |-----|-----------| | `apiClient.jsx` → `apiFetch` | `fetch` + `Authorization` + автоматический refresh по 401 (один refresh на параллельные запросы) | | `useProfile.js` | Загрузка профиля | | `useUpdateProfile.js` | Обновление профиля с аватаром | ### Объявления | Хук | Назначение | |-----|-----------| | `useFeedPosts.jsx` | Лента с пагинацией, request-id guard, дедупликация | | `useCityFeed.jsx` | Лента по городу | | `useSearchPosts.js` | Поиск с дебаунсом | | `useMyPosts.jsx` | Мои объявления | | `useFeedActions.jsx` | Удаление и отметка «найдено» + `error`/`clearError` | | `usePostMutation.js` | Создание/обновление (обычное и `adminMode`) | | `useDeletePost.js`, `usePostStatus.js` | Точечные мутации | | `useImageUpload.js` | Выбор, сжатие, превью изображения | | `useModals.jsx` | Управление модалками | | `useInfiniteScroll.jsx` | Бесконечная прокрутка с cleanup `IntersectionObserver` | | `useCities.js`, `useClickOutside.jsx`, `useIsDesktop.js` | Вспомогательные | ### Реклама | Хук | Назначение | |-----|-----------| | `useFullscreenAdTrigger.js` | Показ фуллскрин-рекламы | | `useAdClickTracking.js`, `useAdImpressionTracking.js` | Клики/показы | | `useTranscodingStatusPolling.js` | Статус транскодинга видео: сброс между роликами, fallback `PROCESSING`, лимит попыток | ### Чат | Хук | Назначение | |-----|-----------| | `useStompClient.js` | Общий STOMP-клиент: reconnect, обработка close/ошибок, стабильные колбэки | | `useChatSocket.js` | Подписка на чат, отправка сообщений | | `useUnreadSocket.js` | Непрочитанные | | `useChats.js` | Список чатов, создание, сообщения (request-id guard) | | `useContactSeller.js` | Создание/получение чата с продавцом | | `useDeleteChat.js` | Удаление чата | ### Карта и локация | Хук | Назначение | |-----|-----------| | `useGeolocation.js` | Геолокация пользователя со статусами | | `useYandexMap.js` | Инициализация карты, метка (drag), обратное геокодирование, ошибка загрузки API | ### Прочее | Хук | Назначение | |-----|-----------| | `useReviews.jsx` | Отзывы: загрузка, отправка, средний рейтинг и распределение | | `useLanguageToggle.js` | Переключение ru/en | --- ## 10. API-эндпоинты Базовый URL — `REACT_APP_API_URL`. Защищённые эндпоинты вызываются через `apiFetch`, который сам добавляет `Authorization: Bearer ` и обновляет токен при 401. ### Аутентификация | Метод | Endpoint | Тело | Ответ | |-------|----------|------|-------| | `POST` | `/api/v1/auth/login` | `{ login, password }` | `{ token, refreshToken, user }` | | `POST` | `/api/v1/auth/register` | `{ name, login, password }` | `{ token, refreshToken, user }` | | `POST` | `/api/v1/auth/refresh` | `{ refreshToken }` | `{ token, refreshToken }` | ### Объявления | Метод | Endpoint | Назначение | |-------|----------|-----------| | `GET` | `/api/v1/posts?page=&size=&…` | Лента с пагинацией (`content` / `items` / массив) | | `GET` | `/api/v1/posts/search?…` | Поиск | | `GET` | `/api/v1/posts/cities` | Список городов | | `GET` | `/api/v1/posts/map?minLat&maxLat&minLng&maxLng&…` | Маркеры в границах карты | | `GET` | `/api/v1/posts/detail/:postId` | Карточка объявления | | `GET` | `/api/v1/posts/:userId` | Объявления пользователя | | `POST` | `/api/v1/posts` | Создать (`FormData`) | | `PUT` | `/api/v1/posts/:id` | Обновить (`FormData`) | | `DELETE` | `/api/v1/posts/:id` | Удалить | | `PATCH` | `/api/v1/posts/:postId/status` | Статус `ACTIVE` / `CLOSED` | ### Профиль | Метод | Endpoint | Назначение | |-------|----------|-----------| | `PUT` | `/api/v1/users/:userId` | Обновить профиль (`FormData`) | ### Чаты | Метод | Endpoint | Назначение | |-------|----------|-----------| | `GET` | `/api/v1/chats` | Список чатов | | `POST` | `/api/v1/chats/with/:otherUserId` | Создать/получить чат (`{ postId }`) | | `GET` | `/api/v1/chats/:chatId/messages` | Сообщения | | `POST` | `/api/v1/chats/:chatId/messages` | Отправить | | `POST` | `/api/v1/chats/:chatId/read` | Отметить прочитанным | | `DELETE` | `/api/v1/chats/:chatId` | Удалить чат | ### Отзывы | Метод | Endpoint | Тело | |-------|----------|------| | `GET` | `/api/v1/reviews` | — | | `POST` | `/api/v1/reviews` | `{ rating, text }` | ### Файлы и реклама | Метод | Endpoint | Назначение | |-------|----------|-----------| | `GET` | `/api/v1/files/:filename` | Изображение по имени | | `GET` | `/api/fullscreen-ad` | Текущая фуллскрин-реклама | | `GET` | `/api/ads/next` | Следующая реклама | | `POST` | `/api/ads/click`, `/api/ads/impression` | Трекинг | Значения категорий в запросах — **стабильные русские строки** (`Вещь`, `Питомец`, `Другое`): они хранятся в БД, поэтому не переводятся вместе с интерфейсом. Локализованы только подписи (`feedConstants.js` → `CATEGORIES`). --- ## 11. WebSocket STOMP поверх SockJS: `${REACT_APP_API_URL}/ws` (токен передаётся при подключении). | Назначение | Путь | |-----------|------| | Подписка на чат | `/topic/chat/{chatId}` | | Подписка на непрочитанные | `/topic/user/{userId}/unread` | | Отправка сообщения | `/app/chat.send` | Общий клиент — `useStompClient`: единый STOMP-клиент с reconnect, обработкой закрытия соединения и ошибок активации; колбэки хранятся в ref, поэтому переподписка не пересоздаёт подключение. --- ## 12. Админские эндпоинты Админ-панель живёт на `/admin/ads` (только для `isAdmin`, только десктоп) и переключается вкладками: - `/admin/ads` — рекламные кампании; - `/admin/ads?tab=posts` — «Все объявления». ### Общие требования - Префикс `/api/v1/admin`, все запросы с `Authorization: Bearer `. - **Роль `ADMIN` обязана проверяться на сервере по каждому эндпоинту.** Клиентский `isAdmin` — только скрытие UI; подделка `localStorage` не должна давать доступ к чужим объявлениям и переписке. - Тело ошибок — JSON `{"message": "..."}`; фронт показывает этот текст. - Пагинация как у `GET /api/v1/posts`: `{ "content": [...], "last": bool, "totalElements": N }`. Фронт принимает и `content`, и `items`, и голый массив. ### Реклама (работает) `/api/v1/admin/campaigns`, `/api/v1/admin/ads`, `/api/v1/admin/fullscreen-ads`. ### Объявления #### `GET /api/v1/admin/posts` Параметры (все опциональны): `page` (с 0), `size` (по умолчанию 20, максимум 100), `search`, `type` (`LOSS`/`FOUND`), `status`. Видны **все** статусы, включая `MODERATION` и `REJECTED`. Сортировка по умолчанию — новые сверху (`createdAt DESC`). `search` ищет без учёта регистра по названию, описанию, городу, району и данным владельца (login, имя, фамилия). Элемент `content` — то же, что у публичного объявления, плюс данные автора (`ownerId`, `ownerLogin`, `ownerName`, `ownerFirstName`, `ownerLastName`). Фронт читает автора с запасом (`ownerLogin` / `authorLogin` / `userLogin` и т. п.). #### `PUT /api/v1/admin/posts/{postId}` `multipart/form-data`, как у пользовательского `PUT /api/v1/posts/{id}`: `type`, `title`, `description`, `category`, `phone`, `reward`, `rewardText`, `images`, `existingImages`, `latitude`, `longitude`, `address`. Ответ `200` — обновлённое объявление. Отличия от пользовательской правки: - **Статус сохраняется** — админ чинит объявление, не убирая его из ленты (`MODERATION` отправил бы на повторную AI-модерацию, `REJECTED` скрыл бы навсегда). Смена статуса — отдельным `PATCH`. - Падение геокодинга не блокирует правку: город берётся из `address`. - Изображения, которых нет в `existingImages`, удаляются. #### `DELETE /api/v1/admin/posts/{postId}` Ответ `204 No Content`. Вместе с объявлением чистятся изображения в хранилище, записи из `favorites` и обнуляется `post_id` в чатах. #### `PATCH /api/v1/admin/posts/{postId}/status` Тело `{ "status": "CLOSED" }` — закрыть («найдено»), `{ "status": "ACTIVE" }` — вернуть в ленту. Принимаются все `PostStatus`: `ACTIVE`, `CLOSED`, `MODERATION`, `REJECTED` (фронт отдаёт первые два). Переход в `CLOSED` увеличивает `posts_found`. Неизвестный статус — `400`. ### Чаты (вкладка не сделана) Требуемые эндпоинты: | Метод | Endpoint | Назначение | |-------|----------|-----------| | `GET` | `/api/v1/admin/chats?page&size&search&postId` | Список чатов с превью | | `GET` | `/api/v1/admin/chats/{chatId}/messages` | Сообщения чата | | `POST` | `/api/v1/admin/chats/{chatId}/messages` | Написать в чат (`{ "content": "…" }`) | | `DELETE` | `/api/v1/admin/chats/{chatId}` | Удалить чат с перепиской | **Важно:** `senderId` должен браться **из JWT, а не из тела запроса**, иначе админ мог бы слать сообщения от имени пользователей. Авторизация — только `ADMIN`; это осознанное расширение прав по сравнению с пользовательским чатом (например, чтобы предупредить о мошенничестве). ### Что сделано на клиенте - `src/api/adminContentApi.js` — `getAdminPosts`, `deleteAdminPost`, `updateAdminPostStatus`; пагинация принимает `content` / `items` / массив; `type` нормализуется в `loss`/`found`. - `CreateUpdatePost` с пропом `adminMode` → `usePostMutation` шлёт `PUT /api/v1/admin/posts/{id}` (форма и валидация общие с пользовательской). - `src/components/admin/PostsTab.jsx` — список, поиск с дебаунсом, фильтры, «Показать ещё», карточки с действиями. ### Что осталось на клиенте - Вкладка «Все чаты» (`ChatsTab.jsx` не создана) — ждёт эндпоинтов чатов. - Проверка роли и скрытие UI: `AuthContext.isAdmin`, `AdminRoute` в `App.js`, `isAdmin` в сайдбаре. Это удобство, не защита. - Админка не постит сообщения через STOMP: используется REST, чтобы реплика не зависела от того, разрешена ли админу подписка `/topic/chat/{id}`. ### Статус бэкенда Репозиторий бэкенда: `D:\reFound\ReFound`. | Файл | Назначение | | --- | --- | | `controller/admin/PostAdminController.java` | Эндпоинты объявлений, у каждого `@PreAuthorize("hasRole('ADMIN')")` | | `service/admin/AdminPostService.java` | Логика: список, правка, удаление, статус | | `dto/admin/AdminPostResponse.java` | Объявление + данные владельца | | `dto/admin/AdminPostStatusUpdateRequest.java` | `{ "status": "..." }` | | `util/PostSpecification.java` | `adminSearch` — поиск по полям объявления и владельца | | `repository/ImageRepository.java` | `findByPostIdIn` — картинки страницы одним запросом вместо N+1 | | `repository/ChatRepository.java` | `clearPostReference` — обнуление `post_id` при удалении | | `test/.../AdminPostServiceTest.java` | 13 тестов: поиск, фильтры, статус, статистика, картинки, кэш | Защита дублируется: `SecurityConfig` требует `ADMIN` на `/api/v1/admin/**`, и методы контроллера дополнительно закрыты `@PreAuthorize`. --- ## 13. Стили и темы Подключение через `src/styles/index.css`: ``` index.css → base.css → layout.css → sidebar.css → card.css → modal.css → ui.css ``` Дополнительно подключаются в компонентах: `chat.css` (`App.js`), `reviews.css`, `rules.css`, `admin.css`, `NakhodkaLanding.css`. CSS-переменные в `base.css`: | Переменная | Значение | Описание | |-----------|----------|----------| | `--teal` | `#2de0c8` | Основной акцент | | `--teal2` | `#1fc9b0` | Тёмный оттенок | | `--border` | `rgba(255,255,255,0.09)` | Границы | | `--muted` / `--muted2` | `rgba(255,255,255,.38)` / `.15` | Второстепенный текст | | `--font` | `Nunito`, sans-serif | Шрифт | | `--err` | `#ff6b6b` | Ошибка | Тёмная тема — градиентные фоны, «стекло» (`backdrop-filter`), акцент `#2de0c8`; светлая переключается через `ThemeContext`. Адаптивность: | Брейкпоинт | Что меняется | |------------|-------------| | `≤ 900px` | Правый сайдбар скрывается, навигация внизу | | `≤ 768px` | Компактные отступы и кнопки | | `≤ 600px` | Модалки на всю ширину | --- ## 14. Основные сценарии **Аутентификация.** Гость нажимает «Разместить», «Мои объявления» или аватар → открывается `AuthModal` → `POST /api/v1/auth/login` → `AuthContext.login()` сохраняет `user`, `token`, `refreshToken` в `localStorage`. Просрочка access-токена обрабатывается прозрачно: `apiFetch` при 401 обновляет токен и повторяет запрос; при неудаче чистит сессию. **Создание объявления.** Авторизованный пользователь → `CreateUpdatePost` → заполняет форму → клиентское сжатие фото → `POST /api/v1/posts` (`FormData`) → лента обновляется. **Поиск и чат.** «Написать» → `POST /api/v1/chats/with/:otherUserId` → переход на `/chats/:chatId` → подписка на `/topic/chat/{chatId}` → сообщения в реальном времени. **Карта.** `useYandexMap` ждёт `window.ymaps` (с таймаутом и ошибкой вместо вечного спиннера), клик по карте ставит метку, drag метки и клик переводят координаты в адрес через Яндекс.Геокодер; запрос на геокодинг защищён request-id от гонок. **Профиль.** Аватар в топбаре → `ProfileModal` → `PUT /api/v1/users/:id` → данные обновляются в `AuthContext` и `localStorage`; «Выйти» — очистка сессии. --- ## 15. Известные проблемы и предупреждения - **`fs.F_OK is deprecated`** при сборке — предупреждение Node 24 из внутренностей `react-scripts@5`. Не влияет на результат; уходит при переходе на CRA 6 / Vite. - **`npm audit`** показывает уязвимости в транзитивных зависимостях CRA (webpack-dev-server и др.). Применять `npm audit fix --force` нельзя: это ломающие обновления в пределах `react-scripts@5`. - **Ключи Яндекса** попадали в историю git — их нужно перевыпустить. - **Клиентская проверка роли** (`isAdmin`, `AdminRoute`) — только скрытие UI. Реальная авторизация обязана быть на сервере по каждому эндпоинту. - **Известная проблема бэкенда:** `GlobalExceptionHandler` ловит `Exception` без исключений, поэтому битый JSON или неверный тип параметра дают `500`, а не `400`. Из-за этого `type` и `status` в админских эндпоинтах принимаются строками и разбираются вручную.