Fix mobile scroll jump, add comments, rename to Котограм

This commit is contained in:
2026-05-29 13:58:42 +03:00
parent 4bf9ea22dd
commit 3664b60f10
16 changed files with 352 additions and 34 deletions

View File

@@ -1,13 +1,14 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useCat, useDeleteCat } from '@/hooks/useCats';
import { useLikeStatus, useLikeCat, useUnlikeCat } from '@/hooks/useLikes';
import { useComments, useAddComment, useDeleteComment } from '@/hooks/useComments';
import { useAuth } from '@/hooks/useAuth';
import { useToast } from '@/components/Toast';
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Skeleton } from '@/components/ui/skeleton';
import { ArrowLeft, Heart, Trash2, Trophy, Image } from 'lucide-react';
import { ArrowLeft, Heart, Trash2, Trophy, Image, Send, X } from 'lucide-react';
export default function CatPage() {
const { id } = useParams<{ id: string }>();
@@ -21,10 +22,16 @@ export default function CatPage() {
const likeMutation = useLikeCat(catId);
const unlikeMutation = useUnlikeCat(catId);
const deleteMutation = useDeleteCat();
const { data: commentsData } = useComments(catId);
const addCommentMutation = useAddComment(catId);
const deleteCommentMutation = useDeleteComment(catId);
const [liked, setLiked] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const [commentText, setCommentText] = useState('');
const commentsEndRef = useRef<HTMLDivElement>(null);
const cat = data?.cat;
const comments = commentsData?.comments ?? [];
const isOwner = cat && user && cat.user_id === user.id;
const isLiked = likeData?.liked ?? false;
@@ -50,6 +57,21 @@ export default function CatPage() {
catch { toast('Ошибка удаления', 'error'); }
};
const handleComment = async () => {
const text = commentText.trim();
if (!text || !user) return;
try {
await addCommentMutation.mutateAsync(text);
setCommentText('');
setTimeout(() => commentsEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100);
} catch { toast('Ошибка', 'error'); }
};
const handleDeleteComment = async (commentId: number) => {
try { await deleteCommentMutation.mutateAsync(commentId); }
catch { toast('Ошибка', 'error'); }
};
if (isLoading) {
return (
<div className="mx-auto max-w-[520px] px-5 py-8">
@@ -141,6 +163,61 @@ export default function CatPage() {
</span>
</div>
{comments.length > 0 && (
<div className="space-y-3">
<p className="text-[11px] font-bold text-[var(--fg-tertiary)] uppercase tracking-widest">
Комментарии · {comments.length}
</p>
{comments.map(c => (
<div key={c.id} className="flex items-start gap-2.5 group">
<Avatar className="h-7 w-7 shrink-0 mt-0.5">
<AvatarFallback className="text-[9px] avatar-colored bg-[var(--fg-tertiary)]">
{c.username.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm leading-[1.5]">
<span className="font-semibold mr-1">@{c.username}</span>
{c.text}
</p>
<p className="text-[10px] text-[var(--fg-tertiary)] mt-0.5">
{new Date(c.created_at).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}
</p>
</div>
{(user && (c.user_id === user.id || user.is_admin)) && (
<button
onClick={() => handleDeleteComment(c.id)}
className="btn-ghost h-6 w-6 text-[var(--fg-tertiary)] hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
>
<X className="h-3 w-3" strokeWidth={2} />
</button>
)}
</div>
))}
<div ref={commentsEndRef} />
</div>
)}
{user && (
<form onSubmit={e => { e.preventDefault(); handleComment(); }} className="flex items-center gap-2">
<input
type="text"
value={commentText}
onChange={e => setCommentText(e.target.value)}
placeholder="Написать комментарий..."
maxLength={500}
className="flex-1 h-9 px-3 text-sm rounded-xl border border-[var(--border)] bg-white placeholder:text-[var(--fg-tertiary)] focus-ring"
/>
<button
type="submit"
disabled={!commentText.trim() || addCommentMutation.isPending}
className="btn-ghost h-9 w-9 text-[var(--accent)] hover:bg-[var(--accent-light)] disabled:opacity-30 disabled:cursor-not-allowed"
>
<Send className="h-4 w-4" strokeWidth={1.5} />
</button>
</form>
)}
<Button variant="outline" size="sm" onClick={() => navigate('/feed')} className="rounded-xl text-xs h-8 px-4 gap-1">
<ArrowLeft className="h-3.5 w-3.5" strokeWidth={1.5} /> В ленту
</Button>