Skip to content

Commit

Permalink
implemented follow and unfollow functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
PrathmeshSadake committed Jun 8, 2023
1 parent 8fc8bb1 commit 42b84af
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 12 deletions.
17 changes: 17 additions & 0 deletions components/posts/CommentFeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CommentItem from "./CommentItem";

interface CommentFeedProps {
comments?: Record<string, any>[];
}

const CommentFeed: React.FC<CommentFeedProps> = ({ comments = [] }) => {
return (
<>
{comments.map((comment: Record<string, any>) => (
<CommentItem key={comment.id} data={comment} />
))}
</>
);
};

export default CommentFeed;
78 changes: 78 additions & 0 deletions components/posts/CommentItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useRouter } from "next/router";
import { useCallback, useMemo } from "react";
import { formatDistanceToNowStrict } from "date-fns";

import Avatar from "../Avatar";

interface CommentItemProps {
data: Record<string, any>;
}

const CommentItem: React.FC<CommentItemProps> = ({ data = {} }) => {
const router = useRouter();

const goToUser = useCallback(
(ev: any) => {
ev.stopPropagation();

router.push(`/users/${data.user.id}`);
},
[router, data.user.id]
);

const createdAt = useMemo(() => {
if (!data?.createdAt) {
return null;
}

return formatDistanceToNowStrict(new Date(data.createdAt));
}, [data.createdAt]);

return (
<div
className='
border-b-[1px]
border-neutral-800
p-5
cursor-pointer
hover:bg-neutral-900
transition
'
>
<div className='flex flex-row items-start gap-3'>
<Avatar userId={data.user.id} />
<div>
<div className='flex flex-row items-center gap-2'>
<p
onClick={goToUser}
className='
text-white
font-semibold
cursor-pointer
hover:underline
'
>
{data.user.name}
</p>
<span
onClick={goToUser}
className='
text-neutral-500
cursor-pointer
hover:underline
hidden
md:block
'
>
@{data.user.username}
</span>
<span className='text-neutral-500 text-sm'>{createdAt}</span>
</div>
<div className='text-white mt-1'>{data.body}</div>
</div>
</div>
</div>
);
};

export default CommentItem;
60 changes: 60 additions & 0 deletions hooks/useLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios from "axios";
import { useCallback, useMemo } from "react";
import { toast } from "react-hot-toast";

import useCurrentUser from "./useCurrentUser";
import useLoginModal from "./useLoginModal";
import usePost from "./usePost";
import usePosts from "./usePosts";

const useLike = ({ postId, userId }: { postId: string; userId?: string }) => {
const { data: currentUser } = useCurrentUser();
const { data: fetchedPost, mutate: mutateFetchedPost } = usePost(postId);
const { mutate: mutateFetchedPosts } = usePosts(userId);

const loginModal = useLoginModal();

const hasLiked = useMemo(() => {
const list = fetchedPost?.likedIds || [];

return list.includes(currentUser?.id);
}, [fetchedPost, currentUser]);

const toggleLike = useCallback(async () => {
if (!currentUser) {
return loginModal.onOpen();
}

try {
let request;

if (hasLiked) {
request = () => axios.delete("/api/like", { data: { postId } });
} else {
request = () => axios.post("/api/like", { postId });
}

await request();
mutateFetchedPost();
mutateFetchedPosts();

toast.success("Success");
} catch (error) {
toast.error("Something went wrong");
}
}, [
currentUser,
hasLiked,
postId,
mutateFetchedPosts,
mutateFetchedPost,
loginModal,
]);

return {
hasLiked,
toggleLike,
};
};

export default useLike;
17 changes: 17 additions & 0 deletions hooks/useNotifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import useSWR from "swr";

import fetcher from "@/libs/fetcher";

const useNotifications = (userId?: string) => {
const url = userId ? `/api/notifications/${userId}` : null;
const { data, error, isLoading, mutate } = useSWR(url, fetcher);

return {
data,
error,
isLoading,
mutate,
};
};

export default useNotifications;
19 changes: 19 additions & 0 deletions hooks/usePost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import useSWR from "swr";

import fetcher from "@/libs/fetcher";

const usePost = (postId: string) => {
const { data, error, isLoading, mutate } = useSWR(
postId ? `/api/posts/${postId}` : null,
fetcher
);

return {
data,
error,
isLoading,
mutate,
};
};

export default usePost;
17 changes: 17 additions & 0 deletions hooks/usePosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import useSWR from "swr";

import fetcher from "@/libs/fetcher";

const usePosts = (userId?: string) => {
const url = userId ? `/api/posts?userId=${userId}` : "/api/posts";
const { data, error, isLoading, mutate } = useSWR(url, fetcher);

return {
data,
error,
isLoading,
mutate,
};
};

export default usePosts;
19 changes: 19 additions & 0 deletions hooks/useUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import useSWR from "swr";

import fetcher from "@/libs/fetcher";

const useCurrentUser = (userId: string) => {
const { data, error, isLoading, mutate } = useSWR(
userId ? `/api/users/${userId}` : null,
fetcher
);

return {
data,
error,
isLoading,
mutate,
};
};

export default useCurrentUser;
6 changes: 1 addition & 5 deletions libs/prismadb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ const globalForPrisma = global as unknown as {
prisma: PrismaClient | undefined;
};

export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: ["query"],
});
export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
1 change: 0 additions & 1 deletion libs/serverAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { prisma } from "./prismadb";

const serverAuth = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);

if (!session?.user?.email) {
throw new Error("Not signed in");
}
Expand Down
2 changes: 2 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "@/styles/globals.css";
import RegisterModal from "@/components/modals/RegisterModal";
import { SessionProvider } from "next-auth/react";
import { Toaster } from "react-hot-toast";
import EditModal from "@/components/modals/EditModal";

const poppins = Poppins({
subsets: ["latin"],
Expand All @@ -23,6 +24,7 @@ export default function App({ Component, pageProps }: AppProps) {
<title>twittergram</title>
</Head>
<Toaster />
<EditModal />
<LoginModal />
<RegisterModal />
<Layout>
Expand Down
9 changes: 3 additions & 6 deletions pages/api/follow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ export default async function handler(
try {
const { userId } = req.body;
const { currentUser } = await serverAuth(req, res);
if (userId || typeof userId !== "string") throw new Error("Invalid ID");

if (!userId || typeof userId !== "string") throw new Error("Invalid ID");
const user = await prisma.user.findUnique({
where: {
id: userId,
Expand All @@ -23,12 +22,10 @@ export default async function handler(
if (!user) throw new Error("Invalid ID");
let updatedFollowingIds = [...(user.followingIds || [])];
if (req.method === "POST") {
updatedFollowingIds.push(currentUser.id);
updatedFollowingIds.push(userId);
}
if (req.method === "DELETE") {
updatedFollowingIds = updatedFollowingIds.filter(
(id) => id !== currentUser.id
);
updatedFollowingIds = updatedFollowingIds.filter((id) => id !== userId);
}

const updatedUser = await prisma.user.update({
Expand Down
57 changes: 57 additions & 0 deletions pages/api/posts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { prisma } from "@/libs/prismadb";
import serverAuth from "@/libs/serverAuth";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST" && req.method !== "GET") {
return res.status(405).end();
}

try {
if (req.method === "POST") {
const { currentUser } = await serverAuth(req, res);
const { body } = req.body;
const post = await prisma.post.create({
data: { body, userId: currentUser.id },
});

return res.status(200).json(post);
}

if (req.method === "GET") {
const { userId } = req.query;
let posts;
if (userId && typeof userId === "string") {
posts = await prisma.post.findMany({
where: {
userId,
},
include: {
user: true,
comments: true,
},
orderBy: {
createdAt: "desc",
},
});
} else {
posts = await prisma.post.findMany({
include: {
user: true,
comments: true,
},
orderBy: {
createdAt: "desc",
},
});
}
return res.status(200).json(posts);
}
} catch (error) {
console.log(error);
return res.status(400).end();
}
}
4 changes: 4 additions & 0 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from "react";
import Header from "../components/Header";
import Form from "@/components/Form";
import PostFeed from "@/components/posts/PostFeed";

const Home = () => {
return (
<div>
<Header label='Home' />
<Form placeholder="What's happening?" />
<PostFeed />
</div>
);
};
Expand Down

0 comments on commit 42b84af

Please sign in to comment.