fix bug and errors

This commit is contained in:
SlimusMinus
2026-10-06 01:00:10 +03:00
parent 73e3634ec4
commit d94a6782a4
50 changed files with 937 additions and 2642 deletions

View File

@@ -106,7 +106,7 @@ function LoginFormInline({ saveAuth, onClose, onSwitch }) {
<p style={{textAlign: "center", marginTop: 14, color: "var(--muted)", fontSize: ".85rem"}}>
{t('auth.noAccount')}{" "}
<button onClick={onSwitch}
<button type="button" onClick={onSwitch}
style={{background: "none", border: "none", color: "var(--teal)", cursor: "pointer", fontWeight: 700, fontSize: ".85rem", fontFamily: "var(--font)",}}
>
{t('auth.register')}
@@ -224,7 +224,7 @@ function RegisterFormInline({ saveAuth, onClose, onSwitch }) {
<p style={{textAlign: "center", marginTop: 14, color: "var(--muted)", fontSize: ".85rem"}}>
{t('auth.hasAccount')}{" "}
<button onClick={onSwitch}
<button type="button" onClick={onSwitch}
style={{background: "none", border: "none", color: "var(--teal)", cursor: "pointer", fontWeight: 700, fontSize: ".85rem", fontFamily: "var(--font)",}}>
{t('auth.loginLink')}
</button>

View File

@@ -2,6 +2,7 @@ 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";
import { whenYmapsReady } from "../utils/ymapsApi";
const MARKER_COLORS = {
LOST: "#ff3b30",
@@ -118,6 +119,10 @@ export default function MapView({
onBack,
}) {
const { t } = useTranslation();
// tRef нужен, чтобы читать перевод из эффектов, не добавляя t в их deps:
// иначе смена языка пересоздавала бы карту и все маркеры с нуля.
const tRef = useRef(t);
tRef.current = t;
const mapRef = useRef(null);
const ymapRef = useRef(null);
const userMarkerRef = useRef(null);
@@ -319,9 +324,20 @@ export default function MapView({
}
};
if (window.ymaps?.ready) {
window.ymaps.ready(init);
}
whenYmapsReady()
.then((ymaps) => {
if (destroyed) return;
ymaps.ready(init);
})
.catch((e) => {
// API карт не подгрузился (заблокирован/офлайн/медленный CDN).
// Без этой ветки пользователь смотрел бы в вечный спиннер без ошибки.
if (!destroyed) {
setError(tRef.current("post.mapLoadFailed"));
setLoading(false);
}
console.warn("Yandex Maps API failed to load:", e);
});
return () => {
destroyed = true;

View File

@@ -1,5 +1,5 @@
// src/components/Toast.jsx
import { useEffect } from "react";
import { useEffect, useRef } from "react";
/**
* Toast — всплывающее сообщение об ошибке/успехе, автоматически скрывается.
@@ -10,10 +10,16 @@ import { useEffect } from "react";
* @param {number} [duration=4000] — время жизни в мс
*/
export default function Toast({ message, type = "error", onDismiss, duration = 4000 }) {
// onDismiss почти всегда — свежий инлайн-стрелки. Если держать его в
// зависимостях useEffect, любой ре-рендер родителя (сообщения чата, поллинг
// рекламы) перезапускал бы отсчёт и тост висел бы бесконечно.
const dismissRef = useRef(onDismiss);
dismissRef.current = onDismiss;
useEffect(() => {
const timer = setTimeout(onDismiss, duration);
const timer = setTimeout(() => dismissRef.current?.(), duration);
return () => clearTimeout(timer);
}, [onDismiss, duration]);
}, [duration]);
if (!message) return null;

View File

@@ -1,28 +0,0 @@
import { useEffect, useState } from "react";
/**
* Отдельный блюр-слой поверх ленты, независим от FeedTopBar.
* Прижат к верху общего контейнера, высота подстраивается под
* реальную высоту topBar (включая safe-area на iOS).
*/
export default function FeedBlurMask({ topBarRef }) {
const [height, setHeight] = useState(56);
useEffect(() => {
const el = topBarRef?.current;
if (!el) return;
const update = () => setHeight(el.getBoundingClientRect().height || 56);
update();
const ro = new ResizeObserver(update);
ro.observe(el);
return () => ro.disconnect();
}, [topBarRef]);
return (
<div
className="f-feed-blur-mask"
style={{ height }}
aria-hidden="true"
/>
);
}

View File

@@ -35,6 +35,9 @@ export default function PullToRefresh({ containerRef, onRefresh, disabled = fals
const el = containerRef.current;
if (!el) return;
el.classList.toggle("f-ptr-active", visible);
// Размонтирование с visible=true иначе навсегда оставило бы маску
// затухания и блокировку pointer-events на контейнере ленты.
return () => el.classList.remove("f-ptr-active");
}, [visible, containerRef]);
useEffect(() => {

View File

@@ -1,21 +0,0 @@
export default function TypeFilterBar({ value, onChange, t }) {
const options = [
{ key: "loss", label: t('feed.filterLoss') },
{ key: null, label: t('feed.filterAll') },
{ key: "found", label: t('feed.filterFound') },
];
return (
<div className="f-filter-bar">
{options.map(opt => (
<button
key={opt.key ?? "all"}
className={`f-filter-btn${value === opt.key ? " active" : ""}`}
onClick={() => onChange(opt.key)}
>
{opt.label}
</button>
))}
</div>
);
}

View File

@@ -1,10 +0,0 @@
export default function HeartIcon({filled}) {
return (
<svg width={14} height={14} viewBox="0 0 24 24"
fill={filled ? "#ff6b6b" : "none"} stroke="#ff6b6b" strokeWidth={2}
strokeLinecap="round">
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
</svg>
);
}

View File

@@ -1,13 +0,0 @@
import magnifier from "../../assets/magnifier.png";
export default function LogoIcon() {
return (
<img
src={magnifier}
alt="logo"
width={20}
height={20}
style={{ objectFit: "contain" }}
/>
);
}

View File

@@ -1,10 +0,0 @@
export default function MapIcon() {
return (
<svg width={14} height={14} viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth={2} strokeLinecap="round">
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
<line x1="8" y1="2" x2="8" y2="18"/>
<line x1="16" y1="6" x2="16" y2="22"/>
</svg>
);
}