diff --git a/client/index.html b/client/index.html index 56f3d1f..aba0304 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 bf72bad..5df0282 100644 --- a/client/src/components/CatCard.tsx +++ b/client/src/components/CatCard.tsx @@ -24,88 +24,86 @@ export default function CatCard({ cat, onOpen }: CatCardProps) { const handleDoubleClick = () => { setShowHeart(true); - setTimeout(() => setShowHeart(false), 600); - if (!liked && !isOwner) { - handleLikeAction(); - } + setTimeout(() => setShowHeart(false), 500); + if (!liked && !isOwner) handleLikeAction(); }; const handleLikeAction = async () => { if (isOwner || !user) return; setAnimating(true); - setTimeout(() => setAnimating(false), 400); + 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) { + if (res.ownerPoints !== undefined && cat.user_id === user.id) updateUser({ ...user, points: res.ownerPoints }); - } - } catch { - setLiked(true); - setLikeCount((c) => c + 1); - } + } catch { setLiked(true); setLikeCount((c) => c + 1); } } else { setLiked(true); setLikeCount((c) => c + 1); - toast('Liked', 'like'); - try { - await likeMutation.mutateAsync(); - } catch { - setLiked(false); - setLikeCount((c) => c - 1); - } + toast('Нравится ♥', 'like'); + try { await likeMutation.mutateAsync(); } + catch { setLiked(false); setLikeCount((c) => c - 1); } } }; - const handleLike = () => { - handleLikeAction(); + 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} - - {new Date(cat.created_at).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - })} - +
+ @{cat.username} + {formatDate(cat.created_at)} +
{/* Image */}
onOpen(cat.id)} > {cat.caption {showHeart && (
- +
)}
{/* Actions */} -
+
-
{/* Likes */} -
- {likeCount.toLocaleString()} likes +
+ {likeCount.toLocaleString('ru-RU')} отметок «Нравится»
{/* Caption */} {cat.caption && ( -
+
@{cat.username} {cat.caption} @@ -140,13 +138,15 @@ export default function CatCard({ cat, onOpen }: CatCardProps) {
)} - {/* Comment link */} - + {/* Comments link */} +
+ +
); } diff --git a/client/src/components/CatModal.tsx b/client/src/components/CatModal.tsx index 91c1004..9ba0d5e 100644 --- a/client/src/components/CatModal.tsx +++ b/client/src/components/CatModal.tsx @@ -24,18 +24,9 @@ export default function CatModal({ catId, onClose }: CatModalProps) { 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]); - - useEffect(() => { - document.body.style.overflow = 'hidden'; - return () => { document.body.style.overflow = ''; }; - }, []); + useEffect(() => { if (likeData) setLiked(likeData.liked); }, [likeData]); + useEffect(() => { if (data?.cat) setLikeCount(data.cat.likes_count); }, [data]); + useEffect(() => { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, []); const cat = data?.cat; const isOwner = cat && user && cat.user_id === user.id; @@ -43,32 +34,23 @@ export default function CatModal({ catId, onClose }: CatModalProps) { const handleLike = async () => { if (isOwner || !user || !cat) return; setAnimating(true); - setTimeout(() => setAnimating(false), 400); + 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) { + if (res.ownerPoints !== undefined && cat.user_id === user.id) updateUser({ ...user, points: res.ownerPoints }); - } refetchLike(); - } catch { - setLiked(true); - setLikeCount((c) => c + 1); - } + } catch { setLiked(true); setLikeCount((c) => c + 1); } } else { setLiked(true); setLikeCount((c) => c + 1); - toast('Liked', 'like'); - try { - await likeMutation.mutateAsync(); - refetchLike(); - } catch { - setLiked(false); - setLikeCount((c) => c - 1); - } + toast('Нравится ♥', 'like'); + try { await likeMutation.mutateAsync(); refetchLike(); } + catch { setLiked(false); setLikeCount((c) => c - 1); } } }; @@ -76,80 +58,70 @@ export default function CatModal({ catId, onClose }: CatModalProps) { if (!isOwner || !cat) return; try { await deleteMutation.mutateAsync(cat.id); - toast('Deleted', 'success'); + toast('Удалено', 'success'); onClose(); - } catch { - toast('Failed to delete', 'error'); - } + } catch { toast('Ошибка удаления', 'error'); } }; if (isLoading || !cat) { return ( -
-
+
+
); } return ( -
+
e.stopPropagation()} > {/* Image */}
- {cat.caption + {cat.caption
- {/* Details panel */} -
-
+ {/* Sidebar */} +
+
- - + + {cat.username.charAt(0).toUpperCase()} - @{cat.username} + @{cat.username}
{isOwner && ( - )} -
- {/* Caption */} -
+ {/* Body */} +
- - + + {cat.username.charAt(0).toUpperCase()}

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

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

@@ -157,7 +129,7 @@ export default function CatModal({ catId, onClose }: CatModalProps) {
{/* Actions */} -
+
-
- {likeCount.toLocaleString()} likes + {likeCount.toLocaleString('ru-RU')} отметок «Нравится» +

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

diff --git a/client/src/components/FeedSidebar.tsx b/client/src/components/FeedSidebar.tsx index e6c5401..82a129b 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 } from 'lucide-react'; +import { Trophy, Cat } from 'lucide-react'; export default function FeedSidebar() { const { user } = useAuth(); @@ -25,44 +25,52 @@ export default function FeedSidebar() {
{user && ( - - + + {user.username.charAt(0).toUpperCase()}
-

{user.username}

-

{user.points} pts

+

{user.username}

+

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

)}
-
- -

Leaderboard

+
+
+ +
+

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

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

No users yet

+

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

) : ( -
+
{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 9d5e429..57a33b2 100644 --- a/client/src/components/Navbar.tsx +++ b/client/src/components/Navbar.tsx @@ -1,7 +1,8 @@ import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '@/hooks/useAuth'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { LogOut, Plus, Home, User } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { LogOut, Plus, Home, Cat } from 'lucide-react'; export default function Navbar() { const { user, logout } = useAuth(); @@ -10,29 +11,40 @@ export default function Navbar() { if (!user) return null; return ( -