From 99ad944eac1d8871a456d8f3e9e345ff59bf684d Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Tue, 1 Sep 2026 01:03:07 +0300 Subject: [PATCH] split pages CreateUpdatePost on two part and fixed button map --- src/components/ClearableField.jsx | 28 ++++++ src/components/NahodkaLogo.jsx | 12 --- src/hooks/useImageUpload.js | 2 +- src/hooks/useYandexMap.js | 27 ++++-- src/locales/en.json | 8 +- src/locales/ru.json | 8 +- src/pages/CreateUpdatePost.jsx | 142 +++++++++++++++++++----------- src/pages/MapModal.jsx | 53 +++++++++++ src/styles/chat.css | 31 +++++++ src/styles/modal.css | 78 ++++++++++++++-- 10 files changed, 307 insertions(+), 82 deletions(-) create mode 100644 src/components/ClearableField.jsx create mode 100644 src/pages/MapModal.jsx diff --git a/src/components/ClearableField.jsx b/src/components/ClearableField.jsx new file mode 100644 index 0000000..cd4ead6 --- /dev/null +++ b/src/components/ClearableField.jsx @@ -0,0 +1,28 @@ +import CloseIcon from "../components/icons/CloseIcon"; + +/** + * ClearableField — обёртка над полем ввода, добавляющая справа крестик, + * который полностью стирает содержимое поля. + * + * @param {function} onClear — очистить поле + * @param {boolean} visible — показывать ли крестик (обычно наличие значения) + * @param {ReactNode} children — само поле ввода (input / textarea) + */ +export default function ClearableField({ onClear, visible, children }) { + return ( +
+ {children} + {visible && ( + + )} +
+ ); +} diff --git a/src/components/NahodkaLogo.jsx b/src/components/NahodkaLogo.jsx index 5abaa15..eb686d3 100644 --- a/src/components/NahodkaLogo.jsx +++ b/src/components/NahodkaLogo.jsx @@ -177,18 +177,6 @@ export default function NahodkaLogo({ fill="none" /> - {/* Блик */} - - - {/* ================================================= */} {/* ЛАПКА */} {/* ================================================= */} diff --git a/src/hooks/useImageUpload.js b/src/hooks/useImageUpload.js index c2d8ea1..db65780 100644 --- a/src/hooks/useImageUpload.js +++ b/src/hooks/useImageUpload.js @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { compressImage } from "../utils/compressImage"; import { getImageUrl } from "../config/authConstants"; -export const MAX_SIZE_MB = 5; +export const MAX_SIZE_MB = 10; export const ACCEPT = "image/jpeg,image/png,image/webp"; /** diff --git a/src/hooks/useYandexMap.js b/src/hooks/useYandexMap.js index 94952a4..17a575d 100644 --- a/src/hooks/useYandexMap.js +++ b/src/hooks/useYandexMap.js @@ -12,9 +12,16 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "") const [coords, setCoords] = useState(initialCoords); const [address, setAddress] = useState(initialAddress ?? ""); const addressRef = useRef(initialAddress ?? ""); + const [loading, setLoading] = useState(false); + // Всегда хранит последние координаты, чтобы при повторной инициализации + // карты (например, при повторном открытии модалки) маркер появлялся там, + // где пользователь его оставил, а не сбрасывался на initialCoords. + const coordsRef = useRef(initialCoords); + useEffect(() => { coordsRef.current = coords; }, [coords]); const resolveAddress = useCallback((lat, lng) => { const requestId = ++requestIdRef.current; + setLoading(true); fetch( `https://geocode-maps.yandex.ru/1.x/?apikey=${YANDEX_GEOCODER_API_KEY}&geocode=${lng},${lat}&format=json&lang=ru_RU` @@ -29,6 +36,7 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "") const resolved = found || `${lat.toFixed(5)}, ${lng.toFixed(5)}`; setAddress(resolved); addressRef.current = resolved; + setLoading(false); }) .catch(() => { if (requestId !== requestIdRef.current) return; @@ -36,6 +44,7 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "") const fallback = `${lat.toFixed(5)}, ${lng.toFixed(5)}`; setAddress(fallback); addressRef.current = fallback; + setLoading(false); }); }, []); @@ -47,8 +56,9 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "") const init = () => { if (ymapRef.current) return; - const center = initialCoords - ? [initialCoords.latitude, initialCoords.longitude] + const startCoords = coordsRef.current; + const center = startCoords + ? [startCoords.latitude, startCoords.longitude] : [55.751574, 37.573856]; const map = new window.ymaps.Map(mapRef.current, { @@ -58,11 +68,11 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "") }); ymapRef.current = map; - if (initialCoords) { + if (startCoords) { const pm = new window.ymaps.Placemark(center, {}, { draggable: true }); map.geoObjects.add(pm); placemarkRef.current = pm; - resolveAddress(initialCoords.latitude, initialCoords.longitude); + resolveAddress(startCoords.latitude, startCoords.longitude); pm.events.add("dragend", () => { const [lat, lng] = pm.geometry.getCoordinates(); @@ -102,12 +112,11 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "") placemarkRef.current = null; initializedRef.current = false; }; - // initialCoords намеренно не в deps — нужен только при первой инициализации, - // а initializedRef защищает от повторного запуска. Объект initialCoords - // пересоздаётся на каждый рендер родителя, поэтому включение его в deps - // приводило бы к пересозданию карты на каждый ввод в форме. + // enabled намеренно в deps — карта инициализируется/разрушается при открытии + // и закрытии модалки. Начальные координаты читаются из coordsRef, чтобы + // при повторном открытии маркер вставал на последний выбранный адрес. // eslint-disable-next-line react-hooks/exhaustive-deps }, [enabled, resolveAddress]); - return { mapRef, coords, address, addressRef }; + return { mapRef, coords, address, addressRef, loading }; } \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index 9b4e423..2253aa9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -102,6 +102,10 @@ "categoryLabel": "Category", "mapLabel": "Location on map", "mapHint": "Click on the map to mark the location", + "mapPickLabel": "Choose on map", + "mapEmpty": "Location not selected yet", + "selectedAddress": "Selected address", + "saveLocation": "Save location", "addressLabel": "Address", "addressPlaceholder": "E.g. 123 Main St, New York", "phoneLabel": "Phone number", @@ -119,7 +123,9 @@ "rulesLinkText": "platform rules", "deleteError": "Failed to delete the ad", "deleteErrorWithStatus": "Failed to delete ad (error {{status}})", - "statusUpdateError": "Failed to change the ad status." + "statusUpdateError": "Failed to change the ad status.", + "nextStep": "Next", + "backStep": "Back" }, "feed": { "loading": "Loading ads...", diff --git a/src/locales/ru.json b/src/locales/ru.json index 62103ce..0f4a87d 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -102,6 +102,10 @@ "categoryLabel": "Категория", "mapLabel": "Место на карте", "mapHint": "Нажмите на карту, чтобы отметить место", + "mapPickLabel": "Указать местоположение на карте", + "mapEmpty": "Место ещё не выбрано", + "selectedAddress": "Выбранный адрес", + "saveLocation": "Сохранить местоположение", "addressLabel": "Адрес", "addressPlaceholder": "Например: Москва, ул. Ленина, 5", "phoneLabel": "Номер телефона", @@ -119,7 +123,9 @@ "rulesLinkText": "правилами размещения площадки", "deleteError": "Не удалось удалить объявление", "deleteErrorWithStatus": "Не удалось удалить объявление (ошибка {{status}})", - "statusUpdateError": "Не удалось изменить статус объявления" + "statusUpdateError": "Не удалось изменить статус объявления", + "nextStep": "Далее", + "backStep": "Назад" }, "feed": { diff --git a/src/pages/CreateUpdatePost.jsx b/src/pages/CreateUpdatePost.jsx index d8ce7fe..ee76aba 100644 --- a/src/pages/CreateUpdatePost.jsx +++ b/src/pages/CreateUpdatePost.jsx @@ -8,7 +8,8 @@ import { useImageUpload, ACCEPT, MAX_SIZE_MB } from "../hooks/useImageUpload"; import { validatePostForm } from "../utils/validatePostForm"; import { formatPhone } from "../utils/formatPhone"; import { useYandexMap } from "../hooks/useYandexMap"; -import { isMobileDevice } from "../utils/isMobileDevice"; +import MapModal from "./MapModal"; +import ClearableField from "../components/ClearableField"; /** * CreateUpdatePost — модальная форма создания и редактирования объявления. @@ -38,7 +39,9 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) { ); const [rulesModalOpen, setRulesModalOpen] = useState(false); + const [mapModalOpen, setMapModalOpen] = useState(false); const [errors, setErrors] = useState({}); + const [step, setStep] = useState(1); // 1 — первая часть, 2 — вторая часть const { images: uploadedImages, @@ -59,8 +62,8 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) { ? { latitude: initialData.latitude, longitude: initialData.longitude } : null; - const { mapRef, coords, address, addressRef } = useYandexMap( - true, + const { mapRef, coords, address, addressRef, loading: mapLoading } = useYandexMap( + mapModalOpen, initialCoords, isEdit ? (initialData.location || "") : "" ); @@ -155,6 +158,8 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) { + {step === 1 && ( + <> {/* Тип: Потеря / Находка */}
@@ -179,26 +184,30 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) { {/* Название */}
- set("title", e.target.value)} - /> + set("title", "")} visible={!!form.title}> + set("title", e.target.value)} + /> + {errors.title &&

{errors.title}

}
{/* Описание */}
-