diff --git a/client/index.html b/client/index.html index aba0304..56f3d1f 100644 --- a/client/index.html +++ b/client/index.html @@ -4,7 +4,7 @@ - Catstagram — фото котов + Catstagram
diff --git a/client/src/components/CatCard.tsx b/client/src/components/CatCard.tsx index 5df0282..2ad97e9 100644 --- a/client/src/components/CatCard.tsx +++ b/client/src/components/CatCard.tsx @@ -2,9 +2,8 @@ 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'; +import { Avatar, AvatarFallback } from '@/components/ui/avatar'; interface CatCardProps { cat: Cat; @@ -15,23 +14,13 @@ 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 () => { + const toggleLike = async () => { if (isOwner || !user) return; - setAnimating(true); - setTimeout(() => setAnimating(false), 450); if (liked) { setLiked(false); @@ -44,109 +33,90 @@ export default function CatCard({ cat, onOpen }: CatCardProps) { } else { setLiked(true); setLikeCount((c) => c + 1); - toast('Нравится ♥', 'like'); + 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 ( -
- {/* Header */} -
-
window.location.href = `/profile/${cat.user_id}`} - > - - - {cat.username.charAt(0).toUpperCase()} - - -
- @{cat.username} - {formatDate(cat.created_at)} +
+
+ {/* Header */} +
+
onOpen(cat.id)} + > + + + {cat.username.charAt(0).toUpperCase()} + + +
+ @{cat.username} + + {new Date(cat.created_at).toLocaleDateString('ru-RU', { + day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', + })} + +
-
- {/* Image */} -
onOpen(cat.id)} - > - {cat.caption - {showHeart && ( -
- + {/* Image */} +
onOpen(cat.id)} + > + {cat.caption +
+ + {/* Caption */} + {cat.caption && ( +
onOpen(cat.id)}> + + @{cat.username} + {cat.caption} +
)} -
- {/* Actions */} -
-
- - + > + + {liked ? '❤️' : '🤍'} + + {likeCount > 0 && {likeCount}} + + +
+ + {cat.user_points > 0 && ( + 🏆 {cat.user_points} + )}
- - {/* Likes */} -
- {likeCount.toLocaleString('ru-RU')} отметок «Нравится» -
- - {/* Caption */} - {cat.caption && ( -
- - @{cat.username} - {cat.caption} - -
- )} - - {/* Comments link */} -
- -
); } diff --git a/client/src/components/CatModal.tsx b/client/src/components/CatModal.tsx index 9ba0d5e..20fea14 100644 --- a/client/src/components/CatModal.tsx +++ b/client/src/components/CatModal.tsx @@ -4,7 +4,7 @@ import { useLikeStatus, useLikeCat, useUnlikeCat } from '@/hooks/useLikes'; import { useAuth } from '@/hooks/useAuth'; import { useToast } from '@/components/Toast'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { Heart, MessageCircle, X, Trash2 } from 'lucide-react'; +import { X } from 'lucide-react'; interface CatModalProps { catId: number; @@ -22,7 +22,6 @@ export default function CatModal({ catId, onClose }: CatModalProps) { const [liked, setLiked] = useState(false); const [likeCount, setLikeCount] = useState(0); - const [animating, setAnimating] = useState(false); useEffect(() => { if (likeData) setLiked(likeData.liked); }, [likeData]); useEffect(() => { if (data?.cat) setLikeCount(data.cat.likes_count); }, [data]); @@ -31,24 +30,20 @@ export default function CatModal({ catId, onClose }: CatModalProps) { const cat = data?.cat; const isOwner = cat && user && cat.user_id === user.id; - const handleLike = async () => { + const toggleLike = async () => { if (isOwner || !user || !cat) 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 }); + if (res.ownerPoints !== undefined && cat.user_id === user.id) updateUser({ ...user, points: res.ownerPoints }); refetchLike(); } catch { setLiked(true); setLikeCount((c) => c + 1); } } else { setLiked(true); setLikeCount((c) => c + 1); - toast('Нравится ♥', 'like'); + toast('Нравится', 'like'); try { await likeMutation.mutateAsync(); refetchLike(); } catch { setLiked(false); setLikeCount((c) => c - 1); } } @@ -65,90 +60,87 @@ export default function CatModal({ catId, onClose }: CatModalProps) { if (isLoading || !cat) { return ( -
-
+
+
+
+
); } return ( -
+
e.stopPropagation()} > - {/* Image */} -
- {cat.caption + {/* Header */} +
+
+ + + {cat.username.charAt(0).toUpperCase()} + + + @{cat.username} +
+
+ {isOwner && ( + + )} + +
- {/* Sidebar */} -
-
-
- - - {cat.username.charAt(0).toUpperCase()} - - - @{cat.username} -
-
- {isOwner && ( - - )} - + {/* Image */} +
+ {cat.caption +
+ + {/* Info */} +
+
+ + + {cat.username.charAt(0).toUpperCase()} + + +
+

+ @{cat.username} + {cat.caption || 'Без подписи'} +

- {/* Body */} -
-
- - - {cat.username.charAt(0).toUpperCase()} - - -
-

- @{cat.username} - {cat.caption || 'Без подписи'} -

-

- {new Date(cat.created_at).toLocaleDateString('ru-RU', { - year: 'numeric', month: 'long', day: 'numeric', - hour: '2-digit', minute: '2-digit', - })} -

-
-
-
+

+ {new Date(cat.created_at).toLocaleDateString('ru-RU', { + year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', + })} +

+
- {/* Actions */} -
-
- - -
- {likeCount.toLocaleString('ru-RU')} отметок «Нравится» -

🏆 {cat.user_points} баллов

+ {/* Actions */} +
+
+ + 🏆 {cat.user_points} баллов
diff --git a/client/src/components/FeedSidebar.tsx b/client/src/components/FeedSidebar.tsx index 82a129b..d2f4725 100644 --- a/client/src/components/FeedSidebar.tsx +++ b/client/src/components/FeedSidebar.tsx @@ -2,7 +2,7 @@ import { useAuth } from '@/hooks/useAuth'; import { useCats } from '@/hooks/useCats'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Link } from 'react-router-dom'; -import { Trophy, Cat } from 'lucide-react'; +import { Trophy } from 'lucide-react'; export default function FeedSidebar() { const { user } = useAuth(); @@ -11,9 +11,8 @@ export default function FeedSidebar() { const usersMap = new Map(); if (data?.cats) { for (const cat of data.cats) { - if (!usersMap.has(cat.user_id)) { + if (!usersMap.has(cat.user_id)) usersMap.set(cat.user_id, { username: cat.username, points: cat.user_points }); - } } } @@ -22,54 +21,52 @@ export default function FeedSidebar() { .slice(0, 5); return ( -
+
{user && ( - - + + {user.username.charAt(0).toUpperCase()}
-

{user.username}

-

🏆 {user.points} баллов

+

{user.username}

+

🏆 {user.points} баллов

)}
-
-
- -
-

Таблица лидеров

+
+ +

Лидеры

- {topUsers.length === 0 ? ( -

Пока нет пользователей

+

Пока нет пользователей

) : ( -
+
{topUsers.map(([id, u], i) => ( - {i + 1} - + {u.username.charAt(0).toUpperCase()} - {u.username} - {u.points} + + {u.username} + + {u.points} ))}
)}
-
+

Catstagram · Делись фото котов

-

© 2026

); diff --git a/client/src/components/Navbar.tsx b/client/src/components/Navbar.tsx index 57a33b2..71fd8dd 100644 --- a/client/src/components/Navbar.tsx +++ b/client/src/components/Navbar.tsx @@ -1,53 +1,47 @@ -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; import { useAuth } from '@/hooks/useAuth'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { Button } from '@/components/ui/button'; -import { LogOut, Plus, Home, Cat } from 'lucide-react'; export default function Navbar() { const { user, logout } = useAuth(); const navigate = useNavigate(); + const location = useLocation(); if (!user) return null; return ( -