From ea0df060e86163b48207b43837eb86b8c9db74fd Mon Sep 17 00:00:00 2001 From: HeagBoKaT Date: Fri, 29 May 2026 10:38:01 +0300 Subject: [PATCH] =?UTF-8?q?UI:=20=D1=82=D1=91=D0=BF=D0=BB=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20+=20=D1=80?= =?UTF-8?q?=D1=83=D1=81=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/index.html | 2 +- client/src/components/CatCard.tsx | 104 +++++++++--------- client/src/components/CatModal.tsx | 109 ++++++++----------- client/src/components/FeedSidebar.tsx | 42 +++++--- client/src/components/Navbar.tsx | 44 +++++--- client/src/components/StoryCircle.tsx | 16 +-- client/src/components/Toast.tsx | 33 +++--- client/src/index.css | 105 +++++++++---------- client/src/pages/CatPage.tsx | 141 +++++++++++++------------ client/src/pages/FeedPage.tsx | 63 +++++------ client/src/pages/LoginPage.tsx | 80 ++++++++------ client/src/pages/ProfilePage.tsx | 103 ++++++++++-------- client/src/pages/RegisterPage.tsx | 102 ++++++++++-------- client/src/pages/UploadPage.tsx | 145 ++++++++++++++------------ server/src/routes/auth.ts | 8 +- server/src/routes/cats.ts | 2 +- server/src/routes/likes.ts | 4 +- 17 files changed, 576 insertions(+), 527 deletions(-) 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 ( -