207 lines
7.0 KiB
JavaScript
207 lines
7.0 KiB
JavaScript
import {useRef, useState, useEffect} from "react";
|
||
import PinIcon from "../components/icons/PinIcon";
|
||
import CalendarIcon from "../components/icons/CalendarIcon";
|
||
import PhoneIcon from "../components/icons/PhoneIcon";
|
||
import HeartIcon from "../components/icons/HeartIcon";
|
||
import TrashIcon from "../components/icons/TrashIcon";
|
||
import {useContactSeller} from "../hooks/useContactSeller";
|
||
import {useAuthContext} from "../context/AuthContext";
|
||
import {getImageUrl} from "../config/authConstants";
|
||
import DateCalendarPopup from "../components/DateCalendarPopup";
|
||
import Toast from "../components/Toast";
|
||
import {useTranslation} from 'react-i18next';
|
||
|
||
export default function PostOnFeed({ ad, liked, likeCountAdj, onLike, onEdit, onDelete, onMarkFound, isOwner, onImageClick, onLocationClick, onAuthRequired }) {
|
||
const { t } = useTranslation();
|
||
const [showCalendar, setShowCalendar] = useState(false);
|
||
const [descExpanded, setDescExpanded] = useState(false);
|
||
const [descTruncated, setDescTruncated] = useState(false);
|
||
const dateAnchorRef = useRef(null);
|
||
const descRef = useRef(null);
|
||
const { isAuthenticated } = useAuthContext();
|
||
const { contactSeller, error: contactError, clearError } = useContactSeller(ad);
|
||
|
||
useEffect(() => {
|
||
const check = () => {
|
||
if (!descExpanded && descRef.current) {
|
||
setDescTruncated(descRef.current.scrollHeight > descRef.current.clientHeight + 1);
|
||
}
|
||
};
|
||
check();
|
||
window.addEventListener("resize", check);
|
||
return () => window.removeEventListener("resize", check);
|
||
}, [descExpanded]);
|
||
|
||
const handleContact = (e) => {
|
||
e.stopPropagation();
|
||
if (!isAuthenticated) {
|
||
if (onAuthRequired) onAuthRequired();
|
||
return;
|
||
}
|
||
contactSeller();
|
||
};
|
||
|
||
const imageUrl = getImageUrl(ad.images?.[0]);
|
||
const address = ad.city;
|
||
const createdAt = ad.createdAt
|
||
? new Date(ad.createdAt).toLocaleDateString("ru-RU")
|
||
: null;
|
||
|
||
const isClosed = ad.status === 'CLOSED';
|
||
|
||
const headAccentClass = ad.type === "found" ? " f-head-found" : " f-head-loss";
|
||
|
||
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>
|
||
);
|
||
|
||
return (
|
||
<div className={`f-card${isClosed ? ' f-card-closed' : ''}`}>
|
||
|
||
<div className={`f-card-head${headAccentClass}`}>
|
||
<div className="f-card-author">
|
||
<div
|
||
className="f-card-author-name"
|
||
>
|
||
{ad.title}
|
||
</div>
|
||
</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) + likeCountAdj}</span>
|
||
</div>
|
||
<div className="f-card-actions-right">
|
||
{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>
|
||
)}
|
||
{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 f-loc-pill"
|
||
title={t('postCard.showOnMap', { address })}
|
||
onClick={e => {
|
||
e.stopPropagation();
|
||
onLocationClick?.(ad);
|
||
}}
|
||
>
|
||
<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>
|
||
)}
|
||
{onMarkFound && isOwner && !isClosed && (
|
||
<button type="button" className="f-card-found-btn"
|
||
onClick={e => {
|
||
e.stopPropagation();
|
||
onMarkFound(ad.id);
|
||
}}>
|
||
{t('postCard.itemFound')}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{contactError && (
|
||
<Toast
|
||
message={contactError}
|
||
type="error"
|
||
onDismiss={clearError}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|