fixed start page and scroll in modal window
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import CloseIcon from "../components/icons/CloseIcon";
|
||||
import RulesModal from "../components/RulesModal";
|
||||
import RulesModal from "./RulesModal";
|
||||
import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePostMutation } from "../hooks/usePostMutation";
|
||||
|
||||
@@ -10,7 +10,7 @@ import ImageViewer from "../components/ImageViewer";
|
||||
import CityFilter from "../components/CityFilter";
|
||||
import { ProfileModal } from "../components/profile";
|
||||
import AuthModal from "../components/AuthModal";
|
||||
import RulesModal from "../components/RulesModal";
|
||||
import RulesModal from "./RulesModal";
|
||||
import FeedTopBar from "../components/feed/FeedTopBar";
|
||||
import FeedSearchBar from "../components/feed/FeedSearchBar";
|
||||
import TypeFilterBar from "../components/feed/TypeFilterBar";
|
||||
@@ -45,6 +45,7 @@ export default function MainFeed() {
|
||||
const [selectedCities, setSelectedCities] = useState([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [liked, setLiked] = useState(new Set());
|
||||
const [likeCountAdj, setLikeCountAdj] = useState({});
|
||||
|
||||
const feedRef = useRef(null);
|
||||
|
||||
@@ -85,15 +86,23 @@ export default function MainFeed() {
|
||||
onAuthRequired: () => modals.open("auth"),
|
||||
isAuthenticated,
|
||||
t,
|
||||
setLiked,
|
||||
setLikeCountAdj,
|
||||
});
|
||||
|
||||
// Загружаем лайки пользователя при авторизации
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && token) {
|
||||
loadFavorites().then(favoriteIds => setLiked(new Set(favoriteIds)));
|
||||
loadFavorites().then(favoriteIds => {
|
||||
const s = new Set(favoriteIds);
|
||||
actions.syncLiked(s);
|
||||
setLiked(s);
|
||||
});
|
||||
} else {
|
||||
actions.syncLiked(new Set());
|
||||
setLiked(new Set());
|
||||
}
|
||||
setLikeCountAdj({});
|
||||
}, [isAuthenticated, token, loadFavorites]);
|
||||
|
||||
// Открываем модалку аутентификации если пришли с лендинга
|
||||
@@ -102,7 +111,7 @@ export default function MainFeed() {
|
||||
modals.open("auth");
|
||||
window.history.replaceState({}, document.title);
|
||||
}
|
||||
}, [location.state, modals]);
|
||||
}, [location.state]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Первичная загрузка ленты (дебаунс поиска), только когда нет фильтра по городам
|
||||
useEffect(() => {
|
||||
@@ -229,6 +238,7 @@ export default function MainFeed() {
|
||||
filteredAds={filteredAds}
|
||||
activeNav={activeNav}
|
||||
liked={liked}
|
||||
likeCountAdj={likeCountAdj}
|
||||
onLike={actions.toggleLike}
|
||||
onEdit={handleEdit}
|
||||
onDelete={actions.handleDelete}
|
||||
|
||||
@@ -11,25 +11,18 @@ import DateCalendarPopup from "../components/DateCalendarPopup";
|
||||
import Toast from "../components/Toast";
|
||||
import {useTranslation} from 'react-i18next';
|
||||
|
||||
export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMarkFound, isOwner, onImageClick, onAuthRequired }) {
|
||||
export default function PostOnFeed({ ad, liked, likeCountAdj, onLike, onEdit, onDelete, onMarkFound, isOwner, onImageClick, onAuthRequired }) {
|
||||
const { t } = useTranslation();
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
const [titleExpanded, setTitleExpanded] = useState(false);
|
||||
const [descExpanded, setDescExpanded] = useState(false);
|
||||
const [titleTruncated, setTitleTruncated] = useState(false);
|
||||
const [descTruncated, setDescTruncated] = useState(false);
|
||||
const dateAnchorRef = useRef(null);
|
||||
const titleRef = useRef(null);
|
||||
const descRef = useRef(null);
|
||||
const { isAuthenticated } = useAuthContext();
|
||||
const { contactSeller, error: contactError, clearError } = useContactSeller(ad);
|
||||
|
||||
// Определяем, обрезан ли title/description (тремя точками), и реагируем на ресайз
|
||||
useEffect(() => {
|
||||
const check = () => {
|
||||
if (!titleExpanded && titleRef.current) {
|
||||
setTitleTruncated(titleRef.current.scrollWidth > titleRef.current.clientWidth + 1);
|
||||
}
|
||||
if (!descExpanded && descRef.current) {
|
||||
setDescTruncated(descRef.current.scrollHeight > descRef.current.clientHeight + 1);
|
||||
}
|
||||
@@ -37,7 +30,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
|
||||
check();
|
||||
window.addEventListener("resize", check);
|
||||
return () => window.removeEventListener("resize", check);
|
||||
}, [titleExpanded, descExpanded]);
|
||||
}, [descExpanded]);
|
||||
|
||||
const handleContact = (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -68,9 +61,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
|
||||
<div className="f-card-head">
|
||||
<div className="f-card-author">
|
||||
<div
|
||||
ref={titleRef}
|
||||
className={`f-card-author-name${titleTruncated ? " f-truncatable" : ""}${titleExpanded ? " f-expanded" : ""}`}
|
||||
onClick={titleTruncated ? () => setTitleExpanded(v => !v) : undefined}
|
||||
className="f-card-author-name"
|
||||
>
|
||||
{ad.title}
|
||||
</div>
|
||||
@@ -100,7 +91,7 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
|
||||
}}>
|
||||
<HeartIcon filled={liked}/>
|
||||
</button>
|
||||
<span className="f-card-like-count">{ad.likeCount || 0}</span>
|
||||
<span className="f-card-like-count">{(ad.likeCount || 0) + likeCountAdj}</span>
|
||||
</div>
|
||||
<div className="f-card-actions-right">
|
||||
{onEdit && isOwner && !isClosed && (
|
||||
|
||||
@@ -6,7 +6,7 @@ import Sidebar from "../components/Sidebar";
|
||||
import AppTopBar from "../components/layout/AppTopBar";
|
||||
import { ProfileModal } from "../components/profile";
|
||||
import AuthModal from "../components/AuthModal";
|
||||
import RulesModal from "../components/RulesModal";
|
||||
import RulesModal from "./RulesModal";
|
||||
import ReviewCard from "../components/reviews/ReviewCard";
|
||||
import ReviewForm from "../components/reviews/ReviewForm";
|
||||
import RatingSummary from "../components/reviews/RatingSummary";
|
||||
|
||||
187
src/pages/RulesModal.jsx
Normal file
187
src/pages/RulesModal.jsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CloseIcon from "../components/icons/CloseIcon";
|
||||
import "../styles/rules.css";
|
||||
|
||||
const RULES = [
|
||||
{
|
||||
num: "1",
|
||||
titleKey: "rules.section1Title",
|
||||
content: (
|
||||
<p>
|
||||
Платформа «Находка» предназначена исключительно для помощи людям в поиске утерянных вещей
|
||||
и возврате найденных предметов их владельцам. Размещая объявление, вы соглашаетесь
|
||||
соблюдать настоящие правила и законодательство Российской Федерации.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "2",
|
||||
titleKey: "rules.section2Title",
|
||||
content: (
|
||||
<ul>
|
||||
<li>Объявления о потере личных вещей, документов, животных, транспортных средств</li>
|
||||
<li>Объявления о находке чужого имущества с целью возврата владельцу</li>
|
||||
<li>Фотографии найденного или утерянного предмета</li>
|
||||
<li>Контактные данные для связи с владельцем или нашедшим</li>
|
||||
</ul>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "3",
|
||||
titleKey: "rules.section3Title",
|
||||
content: (
|
||||
<>
|
||||
<p className="rules-sub-title">3.1. Запрещённый контент</p>
|
||||
<ul>
|
||||
<li>Объявления с целью продажи найденного имущества — это является присвоением чужого имущества и преследуется по <span className="rules-law">ст. 160 УК РФ</span></li>
|
||||
<li>Заведомо ложные объявления и фиктивные находки</li>
|
||||
<li>Объявления с целью мошенничества (<span className="rules-law">ст. 159 УК РФ</span>)</li>
|
||||
<li>Дубликаты одного и того же объявления</li>
|
||||
</ul>
|
||||
<p className="rules-sub-title">3.2. Запрещённые предметы</p>
|
||||
<ul>
|
||||
<li>Оружие, боеприпасы, взрывчатые вещества</li>
|
||||
<li>Наркотические и психотропные вещества</li>
|
||||
<li>Похищенное имущество</li>
|
||||
<li>Документы третьих лиц с целью использования в корыстных целях</li>
|
||||
</ul>
|
||||
<p className="rules-sub-title">3.3. Запрещённые действия</p>
|
||||
<ul>
|
||||
<li>Указывать вознаграждение в обмен на молчание или отказ от обращения в полицию</li>
|
||||
<li>Требовать оплату за возврат найденных документов — паспорт и иные документы подлежат безвозмездному возврату</li>
|
||||
<li>Публиковать персональные данные третьих лиц без их согласия (<span className="rules-law">ст. 137 УК РФ, 152-ФЗ</span>)</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "4",
|
||||
titleKey: "rules.section4Title",
|
||||
content: (
|
||||
<ul>
|
||||
<li>Описание должно быть достоверным и точным</li>
|
||||
<li>Фотографии должны соответствовать описанию</li>
|
||||
<li>Контактные данные должны быть актуальными</li>
|
||||
<li>Место и дата потери или находки указываются максимально точно</li>
|
||||
<li>Запрещено использовать оскорбительные, нецензурные выражения</li>
|
||||
</ul>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "5",
|
||||
titleKey: "rules.section5Title",
|
||||
content: (
|
||||
<>
|
||||
<p>Предложение вознаграждения нашедшему является добровольным и законным правом владельца. При этом:</p>
|
||||
<ul>
|
||||
<li>Размер вознаграждения определяет только владелец вещи</li>
|
||||
<li>Нашедший не вправе требовать вознаграждение как условие возврата</li>
|
||||
<li>Согласно <span className="rules-law">ст. 229 ГК РФ</span> нашедший имеет право на вознаграждение до 20% от стоимости вещи, если владелец публично его объявил</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "6",
|
||||
titleKey: "rules.section6Title",
|
||||
content: (
|
||||
<>
|
||||
<p>Нахождение чужих документов обязывает вас согласно законодательству РФ:</p>
|
||||
<ul>
|
||||
<li>Сдать документы в ближайшее отделение полиции, либо</li>
|
||||
<li>Разместить объявление на платформе для возврата владельцу</li>
|
||||
<li>Хранить документы до момента возврата или передачи в полицию</li>
|
||||
</ul>
|
||||
<p className="rules-warn">Продажа, использование или удержание чужих документов преследуется по закону.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "7",
|
||||
titleKey: "rules.section7Title",
|
||||
content: (
|
||||
<ul>
|
||||
<li>Найденное животное необходимо по возможности обеспечить едой и кровом до возврата владельцу</li>
|
||||
<li>Запрещено размещать объявления о продаже найденных животных</li>
|
||||
<li>Если владелец не найден в течение 6 месяцев, право собственности на животное может перейти к нашедшему согласно <span className="rules-law">ст. 231 ГК РФ</span></li>
|
||||
</ul>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "8",
|
||||
titleKey: "rules.section8Title",
|
||||
content: (
|
||||
<ul>
|
||||
<li>Не публикуйте в объявлении избыточные персональные данные — достаточно способа связи</li>
|
||||
<li>Не раскрывайте данные владельца или нашедшего третьим лицам без их согласия</li>
|
||||
<li>Платформа не несёт ответственности за передачу данных пользователями напрямую</li>
|
||||
</ul>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "9",
|
||||
titleKey: "rules.section9Title",
|
||||
content: (
|
||||
<>
|
||||
<p>Администрация платформы вправе:</p>
|
||||
<ul>
|
||||
<li>Удалить объявление без предупреждения при нарушении правил</li>
|
||||
<li>Заблокировать аккаунт пользователя при систематических нарушениях</li>
|
||||
<li>Передать данные пользователя в правоохранительные органы при наличии признаков противоправных действий</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
num: "10",
|
||||
titleKey: "rules.section10Title",
|
||||
content: (
|
||||
<p>
|
||||
Пользователь самостоятельно несёт ответственность за содержание размещаемых объявлений.
|
||||
Платформа «Находка» является информационным посредником и не несёт ответственности
|
||||
за действия пользователей вне платформы.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function RulesModal({ onClose }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const prev = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => { document.body.style.overflow = prev; };
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}>
|
||||
<div className="f-modal rules-modal">
|
||||
{/* Header */}
|
||||
<div className="f-modal-head">
|
||||
<div className="rules-modal-title-wrap">
|
||||
<span className="rules-modal-icon">📋</span>
|
||||
<span className="f-modal-title">{t('rules.title')}</span>
|
||||
</div>
|
||||
<button className="f-close-btn" onClick={onClose}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Rules list */}
|
||||
<div className="rules-list">
|
||||
{RULES.map(rule => (
|
||||
<div key={rule.num} className="rules-section">
|
||||
<div className="rules-section-head">
|
||||
<span className="rules-num">{rule.num}</span>
|
||||
<span className="rules-title">{t(rule.titleKey)}</span>
|
||||
</div>
|
||||
<div className="rules-body">{rule.content}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user