Files
cats/client/src/components/CatCard.tsx

153 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react';
import { Cat } from '@/api/endpoints';
import { useAuth } from '@/hooks/useAuth';
import { useLikeCat, useUnlikeCat } from '@/hooks/useLikes';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Heart, MessageCircle } from 'lucide-react';
import { useToast } from '@/components/Toast';
interface CatCardProps {
cat: Cat;
onOpen: (id: number) => void;
}
export default function CatCard({ cat, onOpen }: CatCardProps) {
const { user, updateUser } = useAuth();
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(cat.likes_count);
const [animating, setAnimating] = useState(false);
const [showHeart, setShowHeart] = useState(false);
const { toast } = useToast();
const likeMutation = useLikeCat(cat.id);
const unlikeMutation = useUnlikeCat(cat.id);
const isOwner = user && cat.user_id === user.id;
const handleDoubleClick = () => {
setShowHeart(true);
setTimeout(() => setShowHeart(false), 500);
if (!liked && !isOwner) handleLikeAction();
};
const handleLikeAction = async () => {
if (isOwner || !user) return;
setAnimating(true);
setTimeout(() => setAnimating(false), 450);
if (liked) {
setLiked(false);
setLikeCount((c) => c - 1);
try {
const res = await unlikeMutation.mutateAsync();
if (res.ownerPoints !== undefined && cat.user_id === user.id)
updateUser({ ...user, points: res.ownerPoints });
} catch { setLiked(true); setLikeCount((c) => c + 1); }
} else {
setLiked(true);
setLikeCount((c) => c + 1);
toast('Нравится ♥', 'like');
try { await likeMutation.mutateAsync(); }
catch { setLiked(false); setLikeCount((c) => c - 1); }
}
};
const handleLike = () => handleLikeAction();
const formatDate = (dateStr: string) => {
const d = new Date(dateStr);
const now = new Date();
const diff = now.getTime() - d.getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) return 'Сегодня';
if (days === 1) return 'Вчера';
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long' });
};
return (
<div className="animate-fade-in bg-white rounded-2xl shadow-sm border border-border/60 overflow-hidden mb-5">
{/* Header */}
<div className="flex items-center justify-between px-4 py-3">
<div
className="flex items-center gap-2.5 cursor-pointer"
onClick={() => window.location.href = `/profile/${cat.user_id}`}
>
<Avatar className="h-8 w-8 ring-2 ring-offset-1 ring-orange-200">
<AvatarFallback className="text-xs bg-gradient-to-br from-orange-400 to-rose-500 text-white">
{cat.username.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div>
<span className="text-sm font-semibold text-foreground">@{cat.username}</span>
<span className="ml-2 text-xs text-muted-foreground">{formatDate(cat.created_at)}</span>
</div>
</div>
</div>
{/* Image */}
<div
className="relative bg-gradient-to-b from-transparent to-muted/30 cursor-pointer"
onDoubleClick={handleDoubleClick}
onClick={() => onOpen(cat.id)}
>
<img
src={cat.image_url}
alt={cat.caption || 'Фото кота'}
className="w-full max-h-[520px] object-contain select-none"
draggable={false}
loading="lazy"
/>
{showHeart && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<Heart className="heart-burst h-24 w-24 text-white drop-shadow-xl" strokeWidth={1.5} />
</div>
)}
</div>
{/* Actions */}
<div className="px-4 pt-3 pb-1">
<div className="flex items-center gap-4">
<button
onClick={handleLike}
disabled={isOwner || !user}
className={`like-btn ${animating ? 'heart-animate' : ''}`}
>
<Heart
className={`h-6 w-6 transition-all ${
liked ? 'fill-rose-500 text-rose-500' : 'text-foreground hover:text-rose-400'
}`}
strokeWidth={liked ? 2.5 : 1.5}
/>
</button>
<button onClick={() => onOpen(cat.id)} className="hover:text-primary transition-colors">
<MessageCircle className="h-6 w-6" strokeWidth={1.5} />
</button>
</div>
</div>
{/* Likes */}
<div className="px-4 pb-0.5">
<span className="text-sm font-bold">{likeCount.toLocaleString('ru-RU')} отметок «Нравится»</span>
</div>
{/* Caption */}
{cat.caption && (
<div className="px-4 pb-0.5">
<span className="text-sm">
<span className="font-semibold mr-1.5">@{cat.username}</span>
{cat.caption}
</span>
</div>
)}
{/* Comments link */}
<div className="px-4 pb-3">
<button
onClick={() => onOpen(cat.id)}
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Смотреть комментарии
</button>
</div>
</div>
);
}