Merge pull request #65 from SlimusMinus/add-back_from_map
added button back from map and fix post on map
This commit is contained in:
@@ -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 && (
|
||||
<div className="f-mapview-geo f-mapview-geo-warn">Геолокация недоступна</div>
|
||||
)}
|
||||
{onBack && (
|
||||
<button type="button" className="f-mapview-back" onClick={onBack}>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" className="f-mapview-back-icon">
|
||||
<path d="M19 12H5" />
|
||||
<path d="m12 19-7-7 7-7" />
|
||||
</svg>
|
||||
{t('map.backToListings')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -389,5 +389,8 @@
|
||||
"favorites": {
|
||||
"toggleError": "Failed to change the favorite status.",
|
||||
"loadError": "Failed to load favorites"
|
||||
},
|
||||
"map": {
|
||||
"backToListings": "Back to listings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "Назад к объявлениям"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
<div className="f-wrap">
|
||||
{isMapView ? (
|
||||
<MapView focusPostId={focusPostId} focusLat={focusLat} focusLng={focusLng} onPostClick={handleMapPostClick} />
|
||||
<MapView
|
||||
focusPostId={focusPostId}
|
||||
focusLat={focusLat}
|
||||
focusLng={focusLng}
|
||||
onPostClick={handleMapPostClick}
|
||||
onBack={handleMapBack}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="f-feed" ref={feedRef}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user