diff --git a/src/components/MapView.jsx b/src/components/MapView.jsx index 0f95ab8..0c6f4c0 100644 --- a/src/components/MapView.jsx +++ b/src/components/MapView.jsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { API_URL, getImageUrl } from "../config/authConstants"; import { useGeolocation, GEO_STATUS } from "../hooks/useGeolocation"; @@ -40,7 +41,8 @@ function createMarkerLayout(color, imageUrl) { const FOCUS_ZOOM = 16; const COORD_MATCH_TOLERANCE = 0.005; -export default function MapView({ focusPostId, focusLat, focusLng, onPostClick }) { +export default function MapView({ focusPostId, focusLat, focusLng, onPostClick, onBack }) { + const { t } = useTranslation(); const mapRef = useRef(null); const ymapRef = useRef(null); const userMarkerRef = useRef(null); @@ -215,6 +217,15 @@ export default function MapView({ focusPostId, focusLat, focusLng, onPostClick } {geoStatus === GEO_STATUS.UNSUPPORTED && (
Геолокация недоступна
)} + {onBack && ( + + )} ); } \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index dc6a052..b55cb67 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -389,5 +389,8 @@ "favorites": { "toggleError": "Failed to change the favorite status.", "loadError": "Failed to load favorites" + }, + "map": { + "backToListings": "Back to listings" } } diff --git a/src/locales/ru.json b/src/locales/ru.json index d70b682..96608f9 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -8,7 +8,7 @@ "favorites": "Избранное", "items": "Вещи", "pets": "Питомцы", - "chat": "Чат", + "chat": "Чаты по объявлениям", "create": "Создать", "search": "Поиск", "map": "Карта", @@ -179,7 +179,7 @@ "offline": "Отключено", "noMessages": "Нет сообщений", "messagePlaceholder": "Сообщение...", - "chats": "Чаты", + "chats": "Чаты по объявлениям", "loginToSee": "Войдите, чтобы видеть чаты", "loading": "Загрузка...", "noChats": "У вас пока нет чатов", @@ -390,5 +390,8 @@ "favorites": { "toggleError": "Не удалось изменить статус избранного", "loadError": "Не удалось загрузить избранное" + }, + "map": { + "backToListings": "Назад к объявлениям" } } diff --git a/src/pages/MainFeed.jsx b/src/pages/MainFeed.jsx index 99e13db..da8558e 100644 --- a/src/pages/MainFeed.jsx +++ b/src/pages/MainFeed.jsx @@ -55,6 +55,9 @@ export default function MainFeed() { const [favoritesLoading, setFavoritesLoading] = useState(false); const feedRef = useRef(null); + // Состояние ленты перед переходом на карту — чтобы вернуть юзера на то же место + const feedStateRef = useRef({ scrollTop: 0, url: "/feed", activeNav: "home" }); + const restoreScrollRef = useRef(false); const isSearchView = new URLSearchParams(location.search).get("view") === "search"; const isMapView = new URLSearchParams(location.search).get("view") === "map"; @@ -230,18 +233,44 @@ export default function MainFeed() { }, [isSearchView, isMapView, navigate]); // Карта с объявлениями + const saveFeedState = useCallback(() => { + feedStateRef.current = { + scrollTop: feedRef.current?.scrollTop ?? 0, + url: `${location.pathname}${location.search}`, + activeNav, + }; + restoreScrollRef.current = true; + }, [location.pathname, location.search, activeNav]); + const handleMapClick = useCallback(() => { + saveFeedState(); setActiveNav("map"); if (!isMapView) navigate("/feed?view=map"); - feedRef.current?.scrollTo({ top: 0, behavior: "smooth" }); - }, [isMapView, navigate]); + }, [isMapView, navigate, saveFeedState]); const handleMapPostClick = useCallback((postId) => { modals.open("postDetail", { postId }); }, [modals]); + // Возврат с карты на ленту — на то же место, где юзер был до открытия карты + const handleMapBack = useCallback(() => { + const { url, activeNav: prevNav } = feedStateRef.current; + setActiveNav(prevNav || "home"); + navigate(url || "/feed", { replace: true }); + }, [navigate]); + + // Восстанавливаем прокрутку ленты после возврата с карты + useEffect(() => { + if (isMapView || !restoreScrollRef.current) return; + const el = feedRef.current; + if (el) el.scrollTop = feedStateRef.current.scrollTop; + restoreScrollRef.current = false; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isMapView, feed.ads, cityFeed.cityAds, feed.loading, cityFeed.cityLoading]); + // Клик по локации на карточке — карта с фокусом на этом объявлении const handleLocationClick = useCallback((ad) => { + saveFeedState(); const params = new URLSearchParams({ view: "map", post: ad.id }); if (ad.latitude != null && ad.longitude != null) { params.set("lat", ad.latitude); @@ -249,8 +278,7 @@ export default function MainFeed() { } setActiveNav("map"); navigate(`/feed?${params.toString()}`); - feedRef.current?.scrollTo({ top: 0, behavior: "smooth" }); - }, [navigate]); + }, [navigate, saveFeedState]); const handleProfileClick = useCallback(() => { if (!isAuthenticated) modals.open("auth"); @@ -332,7 +360,13 @@ export default function MainFeed() {
{isMapView ? ( - + ) : ( <>
diff --git a/src/styles/layout.css b/src/styles/layout.css index 7049e64..655613c 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -456,6 +456,50 @@ .f-mapview-geo-info { color: var(--muted); } .f-mapview-geo-warn { color: var(--err); border-color: rgba(237,73,86,.35); } +/* Back-to-feed button on map view */ +.f-mapview-back { + position: absolute; + bottom: 24px; left: 50%; + transform: translateX(-50%); + z-index: 8; + display: flex; align-items: center; gap: 8px; + padding: 12px 22px; + border: 1px solid rgba(255, 255, 255, .12); + border-radius: 999px; + background: rgba(20, 20, 20, .55); + backdrop-filter: blur(30px) saturate(1.6); -webkit-backdrop-filter: blur(30px) saturate(1.6); + color: #fff; + font-family: var(--font); + font-size: .92rem; font-weight: 700; + cursor: pointer; + white-space: nowrap; + box-shadow: 0 8px 32px rgba(0,0,0,.3), inset 0 0.5px 0 rgba(255,255,255,.08); + transition: background .15s, border-color .15s, transform .15s, box-shadow .15s; +} +.f-mapview-back-icon { width: 18px; height: 18px; flex-shrink: 0; } + +:root:not([data-theme="dark"]) .f-mapview-back { + background: rgba(255, 255, 255, .6); + border-color: rgba(0, 0, 0, .08); + color: var(--text-primary); + box-shadow: 0 8px 32px rgba(0,0,0,.08), inset 0 0.5px 0 rgba(255,255,255,.7); +} + +.f-mapview-back:hover { + background: rgba(255, 255, 255, .22); + border-color: rgba(255, 255, 255, .28); + transform: translateX(-50%) translateY(-2px); + box-shadow: 0 10px 36px rgba(0,0,0,.35), inset 0 0.5px 0 rgba(255,255,255,.12); +} +:root:not([data-theme="dark"]) .f-mapview-back:hover { + background: rgba(255, 255, 255, .85); + border-color: rgba(0, 0, 0, .14); +} +.f-mapview-back:active { + transform: translateX(-50%) translateY(0); + box-shadow: 0 4px 16px rgba(0,0,0,.2), inset 0 0.5px 0 rgba(255,255,255,.08); +} + /* ── Feed Wrap + Scroll ── */ .f-wrap { display: flex; flex: 1; overflow: hidden; min-height: 0; } @@ -481,6 +525,11 @@ } @media (max-width: 900px) { + .f-mapview-back { + /* Поднимаем кнопку выше плавающей нижней навигации */ + bottom: calc(84px + env(safe-area-inset-bottom)); + } + .f-feed { padding: 10px; gap: 12px; diff --git a/src/styles/modal.css b/src/styles/modal.css index cc09a3f..b782825 100644 --- a/src/styles/modal.css +++ b/src/styles/modal.css @@ -522,21 +522,33 @@ select.f-m-field::selection { background: transparent; } padding: 0; overflow: hidden; border-radius: 24px; + display: flex; + flex-direction: column; + max-height: min(90vh, 820px); } .f-post-detail-modal .f-card { border: none; border-radius: 24px; box-shadow: none; + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow-y: auto; + scrollbar-width: thin; } +.f-post-detail-modal .f-card::-webkit-scrollbar { width: 4px; } +.f-post-detail-modal .f-card::-webkit-scrollbar-track { background: transparent; } +.f-post-detail-modal .f-card::-webkit-scrollbar-thumb { background: var(--muted2); border-radius: 2px; } .f-post-detail-modal .f-card-head { border-radius: 24px 24px 0 0; -} -.f-post-detail-modal .f-card-head { + flex-shrink: 0; padding: 10px 14px; } .f-post-detail-modal .f-card-img { max-height: none; overflow: visible; + flex-shrink: 0; } .f-post-detail-modal .f-card-img img { width: 100%; @@ -546,8 +558,25 @@ select.f-m-field::selection { background: transparent; } .f-post-detail-modal .f-card-caption { padding: 8px 14px; } +.f-post-detail-modal .f-card-caption-text { + -webkit-line-clamp: unset; + line-clamp: unset; + overflow: visible; +} .f-post-detail-modal .f-card-foot { - padding: 8px 14px; + position: sticky; + bottom: 0; + z-index: 2; + flex-shrink: 0; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 10px 16px calc(12px + env(safe-area-inset-bottom, 0px)); + background: var(--bg-card); + border-top: 1px solid var(--border); + box-shadow: 0 -6px 20px rgba(0, 0, 0, .06); } .f-app.f-map-view .f-card-foot .f-card-phone { font-size: .75rem;