Skip to content

Commit

Permalink
feat(client): add follow function for users
Browse files Browse the repository at this point in the history
  • Loading branch information
lareii committed Sep 11, 2024
1 parent 2665c56 commit 283641f
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 25 deletions.
4 changes: 3 additions & 1 deletion client/app/app/layout-content.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export default function LayoutContent({ children }) {
const pathname = usePathname();
const loading = useLoadingStore((state) => state.loading);

const isUsersPage = pathname.includes('/app/users');

if (loading) return <Loading />;

return (
Expand All @@ -19,7 +21,7 @@ export default function LayoutContent({ children }) {
<div className='w-full lg:max-w-[calc(1024px-240px-(3*1.25rem))] max-lg:p-5 pb-5'>
<AnimatePresence mode='wait'>
<motion.div
key={pathname.split('/')[2]}
key={isUsersPage ? pathname.split('/')[3] : pathname.split('/')[2]}
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ ease: 'easeInOut', duration: 0.3 }}
Expand Down
92 changes: 71 additions & 21 deletions client/app/app/users/[slug]/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,43 @@
import { useState, useEffect } from 'react';
import { CalendarFold, Sparkle, Trash } from 'lucide-react';
import { getUser, getUserPosts } from '@/lib/api/users';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import { useToast } from '@/components/ui/use-toast';
import PostList from '@/components/app/Post/List';
import Follows from '@/components/app/User/Follows';
import { followUser } from '@/lib/api/users';
import { useAuthStore } from '@/stores/auth';

export default function Page({ params }) {
const me = useAuthStore((state) => state.user);
const [user, setUser] = useState(null);
const { toast } = useToast();

const handleFollow = async (e, user) => {
e.preventDefault();

const response = await followUser(user.username);
if (!response) {
toast({
title: 'hay aksi, bir şeyler ters gitti!',
description:
'sunucudan yanıt alınamadı. lütfen daha sonra tekrar deneyin.',
duration: 3000
});
return;
}

const followersList = user.followers || [];

setUser({
...user,
followers: followersList.includes(me.id)
? followersList.filter((id) => id !== me.id)
: [...followersList, me.id]
});
};

const fetchPosts = async (offset) => {
return await getUserPosts(params.slug, 11, offset);
};
Expand Down Expand Up @@ -39,7 +68,7 @@ export default function Page({ params }) {
fetchUser();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[params]
[]
);

return (
Expand All @@ -53,32 +82,53 @@ export default function Page({ params }) {
{user ? (
<>
<div className='mt-3 mb-5'>
<div className='flex gap-2'>
<div className='text-xl font-bold'>{user.name}</div>
<div className='flex justify-between'>
<div>
<div className='text-xl font-bold'>{user.name}</div>
<div className='text-zinc-400 text-sm'>
@{user.username}
</div>
</div>
{me.id !== user.id && (
<Button
variant={
(user.followers || []).includes(me.id)
? 'secondary'
: ''
}
onClick={(e) => handleFollow(e, user)}
>
{(user.followers || []).includes(me.id)
? 'takipten çık'
: 'takip et'}
</Button>
)}
</div>
<div className='text-zinc-400 text-sm'>@{user.username}</div>
</div>
<div>
<div className='text-sm mb-2'>{user.about}</div>
<div className='flex items-center gap-2 text-xs text-zinc-400'>
<div className='flex'>
<CalendarFold className='w-4 h-4 mr-1' />
{new Date(user.created_at.T * 1000).toLocaleDateString(
'tr-TR',
{ year: 'numeric', month: 'long', day: 'numeric' }
)}
</div>
{' · '}
<div className='text-yellow-500 relative'>
<Sparkle className='w-2.5 h-2.5 absolute right-10 -bottom-1 text-yellow-500 fill-yellow-500 animate-sparkle1' />
<Sparkle className='w-2 h-2 absolute right-2 -top-1 text-yellow-500 fill-yellow-500 animate-sparkle2' />
<Sparkle className='w-3 h-3 absolute -right-2.5 -bottom-1 text-yellow-500 fill-yellow-500 animate-sparkle3' />
<div className='flex flex-col gap-5'>
<div className='flex flex-col gap-2'>
<div className='text-sm'>{user.about}</div>
<div className='flex items-center gap-2 text-xs text-zinc-400'>
<div className='flex'>
<Trash className='w-4 h-4 mr-1' />
<div className='static'>{user.points} çöp puanı</div>
<CalendarFold className='w-4 h-4 mr-1' />
{new Date(user.created_at.T * 1000).toLocaleDateString(
'tr-TR',
{ year: 'numeric', month: 'long', day: 'numeric' }
)}
</div>
{' · '}
<div className='text-yellow-500 relative'>
<Sparkle className='w-2.5 h-2.5 absolute right-10 -bottom-1 text-yellow-500 fill-yellow-500 animate-sparkle1' />
<Sparkle className='w-2 h-2 absolute right-2 -top-1 text-yellow-500 fill-yellow-500 animate-sparkle2' />
<Sparkle className='w-3 h-3 absolute -right-2.5 -bottom-1 text-yellow-500 fill-yellow-500 animate-sparkle3' />
<div className='flex'>
<Trash className='w-4 h-4 mr-1' />
<div className='static'>{user.points} çöp puanı</div>
</div>
</div>
</div>
</div>
<Follows user={user} />
</div>
</>
) : (
Expand Down
6 changes: 4 additions & 2 deletions client/components/app/Post/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ export default function PostList({ fetchPosts }) {
};

fetchInitialPosts();
}, [fetchPosts, toast]);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]);

return (
<div className='flex flex-col gap-2'>
Expand All @@ -96,4 +98,4 @@ export default function PostList({ fetchPosts }) {
)}
</div>
);
}
}
19 changes: 19 additions & 0 deletions client/components/app/User/Follows.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function Follows({ user }) {
return (
<div className='flex gap-2 text-sm text-muted-foreground'>
<div>
<span className='text-primary'>
{user.followers ? user.followers.length : 0}
</span>{' '}
takipçi
</div>
{' · '}
<div>
<span className='text-primary'>
{user.following ? user.following.length : 0}
</span>{' '}
takip edilen
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion client/components/app/User/Hover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export default function Hover({ user }) {
<HoverCardTrigger className='text-sm hover:underline' asChild>
<Link href={`/app/users/${user.username}`}>{user.name}</Link>
</HoverCardTrigger>
<HoverCardContent className='flex flex-col'>
{/* TODO: set z-index to make the hover card appear on top */}
<HoverCardContent className='flex flex-col' align='start'>
<div className='flex items-start'>
<div className='mr-3 w-10 h-10 rounded-lg bg-zinc-800'></div>
<div className='flex flex-col'>
Expand Down
9 changes: 9 additions & 0 deletions client/lib/api/users/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ export async function getUsers(limit, offset) {
}
}

export async function followUser(slug) {
try {
const response = await api.post(`/users/${slug}/follows`);
return response;
} catch (error) {
return error.response;
}
}

export async function getUserPosts(slug, limit, offset) {
try {
const response = await api.get(`/users/${slug}/posts`, {
Expand Down

0 comments on commit 283641f

Please sign in to comment.