change design

This commit is contained in:
SlimusMinus
2026-08-17 23:17:30 +03:00
parent ed92e45f84
commit f02b25e8f3
24 changed files with 1199 additions and 799 deletions

View File

@@ -1,4 +1,4 @@
import {useRef, useState} from "react";
import {useRef, useState, useEffect} from "react";
import PinIcon from "../components/icons/PinIcon";
import CalendarIcon from "../components/icons/CalendarIcon";
import PhoneIcon from "../components/icons/PhoneIcon";
@@ -14,10 +14,31 @@ import {useTranslation} from 'react-i18next';
export default function PostOnFeed({ ad, liked, 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);
}
};
check();
window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check);
}, [titleExpanded, descExpanded]);
const handleContact = (e) => {
e.stopPropagation();
if (!isAuthenticated) {
@@ -35,131 +56,137 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
const isClosed = ad.status === 'CLOSED';
const roleClass = ad.userRole === 'LOST_AND_FOUND' ? 'f-card-role-lost-found'
: ad.userRole === 'METRO' ? 'f-card-role-metro' : '';
const roleBadge = ad.userRole === 'LOST_AND_FOUND' ? (
<div className="f-role-badge f-role-lost-found" title={t('postCard.lostFoundBureau')}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M12 6v6l4 2"/>
</svg>
{t('postCard.lostFoundBureau')}
const typeBadge = (
<div className={`f-type-badge ${ad.type === "loss" ? "f-type-loss" : "f-type-found"}`}>
{ad.type === "loss" ? t('postCard.loss') : t('postCard.found')}
</div>
) : ad.userRole === 'METRO' ? (
<div className="f-role-badge f-role-metro" title={t('postCard.metro')}>
<span className="f-role-metro-letter">М</span>
{t('postCard.metro')}
</div>
) : null;
);
return (
<div className={`f-card${isClosed ? ' f-card-closed' : ''}${roleClass ? ' ' + roleClass : ''}`}>
<div className={`f-card${isClosed ? ' f-card-closed' : ''}`}>
<div className="f-card-inner">
<div className="f-card-img" onClick={e => { e.stopPropagation(); if (onImageClick) onImageClick(ad.images, 0, ad); }}>
{ad.reward && (
<div className="f-card-reward">
💰 {t("postCard.reward")}
</div>
)}
{imageUrl
? <img src={imageUrl} alt={ad.title} />
: <span className="f-card-emoji">{ad.emoji || "📋"}</span>
}
</div>
<div className="f-card-body">
<div className="f-card-badges">
{ad.category && <div className="f-cat-badge">{ad.category}</div>}
<div className={`f-type-badge ${ad.type === "loss" ? "f-type-loss"
: "f-type-found"}`}>
{ad.type === "loss" ? t('postCard.loss') : t('postCard.found')}
</div>
{roleBadge}
</div>
<div className="f-card-title">
<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}
>
{ad.title}
{isClosed && <span className="f-status-badge f-status-closed">{t(
'postCard.foundStatus')}</span>}
</div>
<div className="f-card-desc">{ad.description}</div>
<div className="f-card-meta">
{address && <div className="f-meta-pill"><PinIcon/> {address}
</div>}
{createdAt && (
<div
className="f-meta-pill f-date-pill"
ref={dateAnchorRef}
onClick={e => {
e.stopPropagation();
setShowCalendar(s => !s);
}}
>
<CalendarIcon/> {createdAt}
{showCalendar && (
<DateCalendarPopup
date={ad.createdAt}
onClose={() => setShowCalendar(false)}
anchorRef={dateAnchorRef}
/>
)}
</div>
)}
</div>
<div className="f-card-foot">
<div className="f-card-foot-left">
{ad.phone && (
<a className="f-card-phone" href={`tel:${ad.phone}`}
onClick={e => e.stopPropagation()}>
<PhoneIcon/> {ad.phone}
</a>
)}
</div>
<div className="f-card-foot-right">
{!isOwner && !isClosed && (
<button type="button" className="f-card-contact"
onClick={handleContact}>
{t('postCard.write')}
</button>
)}
{onEdit && isOwner && !isClosed && (
<button type="button" className="f-card-edit-btn"
onClick={e => {
e.stopPropagation();
onEdit(ad);
}}>
✏️
</button>
)}
{onDelete && isOwner && (
<button type="button" className="f-card-delete-btn"
onClick={e => {
e.stopPropagation();
onDelete(ad.id);
}}>
<TrashIcon/>
</button>
)}
{onMarkFound && isOwner && !isClosed && (
<button type="button" className="f-card-found-btn"
onClick={e => {
e.stopPropagation();
onMarkFound(ad.id);
}}>
{t('postCard.itemFound')}
</button>
)}
<button type="button"
className={`f-like-btn${liked ? " liked" : ""}`}
</div>
<div className="f-card-head-side">
{typeBadge}
{isClosed && (
<span className="f-status-badge f-status-closed">{t('postCard.foundStatus')}</span>
)}
</div>
</div>
<div className="f-card-img" onClick={e => { e.stopPropagation(); if (onImageClick) onImageClick(ad.images, 0, ad); }}>
{imageUrl
? <img src={imageUrl} alt={ad.title} />
: <span className="f-card-emoji">{ad.emoji || "📋"}</span>
}
</div>
<div className="f-card-actions">
<div className="f-card-actions-left">
<button type="button"
className={`f-card-icon-btn${liked ? " liked" : ""}`}
onClick={e => {
e.stopPropagation();
onLike(ad.id);
}}>
<HeartIcon filled={liked}/>
</button>
<span className="f-card-like-count">{ad.likeCount || 0}</span>
</div>
<div className="f-card-actions-right">
{onEdit && isOwner && !isClosed && (
<button type="button" className="f-card-edit-btn"
onClick={e => {
e.stopPropagation();
onLike(ad.id);
onEdit(ad);
}}>
<HeartIcon filled={liked}/>
{ad.likeCount || 0}
✏️
</button>
)}
{onDelete && isOwner && (
<button type="button" className="f-card-delete-btn"
onClick={e => {
e.stopPropagation();
onDelete(ad.id);
}}>
<TrashIcon/>
</button>
)}
{ad.reward && (
<span className="f-card-reward-text">
{t("postCard.reward")}
</span>
)}
</div>
</div>
<div className="f-card-caption">
<span
ref={descRef}
className={`f-card-caption-text${descTruncated ? " f-truncatable" : ""}${descExpanded ? " f-expanded" : ""}`}
onClick={descTruncated ? () => setDescExpanded(v => !v) : undefined}
>
{ad.description}
</span>
</div>
<div className="f-card-meta">
{address && <div className="f-meta-pill"><PinIcon/> {address}</div>}
{createdAt && (
<div
className="f-meta-pill f-date-pill"
ref={dateAnchorRef}
onClick={e => {
e.stopPropagation();
setShowCalendar(s => !s);
}}
>
<CalendarIcon/> {createdAt}
{showCalendar && (
<DateCalendarPopup
date={ad.createdAt}
onClose={() => setShowCalendar(false)}
anchorRef={dateAnchorRef}
/>
)}
</div>
</div>
)}
</div>
<div className="f-card-foot">
<div className="f-card-foot-left">
{ad.phone && (
<a className="f-card-phone" href={`tel:${ad.phone}`}
onClick={e => e.stopPropagation()}>
<PhoneIcon/> {ad.phone}
</a>
)}
</div>
<div className="f-card-foot-right">
{!isOwner && !isClosed && (
<button type="button" className="f-card-contact"
onClick={handleContact}>
{t('postCard.write')}
</button>
)}
{onMarkFound && isOwner && !isClosed && (
<button type="button" className="f-card-found-btn"
onClick={e => {
e.stopPropagation();
onMarkFound(ad.id);
}}>
{t('postCard.itemFound')}
</button>
)}
</div>
</div>
@@ -172,4 +199,4 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
)}
</div>
);
}
}