Skip to content

Commit d3f73a2

Browse files
committedApr 21, 2023
Notifications
1 parent 775610a commit d3f73a2

11 files changed

+180
-15
lines changed
 

‎components/Avatar.tsx

+11-9
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ function Avatar({ userId, isLarge, hasBorder }: Props) {
2121
[router, userId]
2222
);
2323
return (
24-
<div
25-
className={`
24+
<div>
25+
<div
26+
className={`
2627
${hasBorder ? 'border-4 border-black' : ''}
2728
${isLarge ? 'h-32' : 'h-12'}
2829
${isLarge ? 'w-32' : 'w-12'}
@@ -32,13 +33,14 @@ function Avatar({ userId, isLarge, hasBorder }: Props) {
3233
cursor-pointer
3334
relative
3435
`}>
35-
<Image
36-
fill
37-
style={{ objectFit: 'cover', borderRadius: '100%' }}
38-
onClick={onClick}
39-
alt='Avatar'
40-
src={fetchedUser?.profileImage || '/images/placeholder.png'}
41-
/>
36+
<Image
37+
fill
38+
style={{ objectFit: 'cover', borderRadius: '100%' }}
39+
onClick={onClick}
40+
alt='Avatar'
41+
src={fetchedUser?.profileImage || '/images/placeholder.png'}
42+
/>
43+
</div>
4244
</div>
4345
);
4446
}

‎components/NotificationsFeed.tsx

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import useCurrentUser from '@/hooks/useCurrentUser';
2+
import useNotifications from '@/hooks/useNotifications';
3+
import { useEffect } from 'react';
4+
import { BsTwitter } from 'react-icons/bs';
5+
6+
type Props = {};
7+
8+
function NotificationsFeed({}: Props) {
9+
const { data: currentUser, mutate: mutateCurrentUser } = useCurrentUser();
10+
const { data: fetchedNotifications = [] } = useNotifications(currentUser?.id);
11+
12+
useEffect(() => {
13+
mutateCurrentUser();
14+
}, [mutateCurrentUser]);
15+
if (fetchedNotifications.length === 0) {
16+
return <div className='text-neutral-600 text-center p-6 text-xl'>No notifications</div>;
17+
}
18+
19+
return (
20+
<div className='flex flex-col'>
21+
{fetchedNotifications.map((notification: Record<string, any>) => (
22+
<div
23+
className='flex flex-row items-center p-6 gap-4 border-b-[1px] border-neutral-800'
24+
key={notification.id}>
25+
<BsTwitter color='white' size={32} />
26+
<p className='text-white'>{notification.body}</p>
27+
</div>
28+
))}
29+
</div>
30+
);
31+
}
32+
33+
export default NotificationsFeed;

‎components/layout/Sidebar.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function Sidebar({}: Props) {
2020
href: '/notifications',
2121
icon: BsBellFill,
2222
auth: true,
23+
alert: currentUser?.hasNotification,
2324
},
2425
{
2526
label: 'Profile',
@@ -40,6 +41,7 @@ function Sidebar({}: Props) {
4041
label={item.label}
4142
icon={item.icon}
4243
auth={item.auth}
44+
alert={item.alert}
4345
/>
4446
))}
4547
{currentUser && <SidebarItem onClick={() => signOut()} icon={BiLogOut} label='Logout' />}

‎components/layout/SidebarItem.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import useLoginModal from '@/hooks/useLoginModal';
33
import { useRouter } from 'next/router';
44
import React, { useCallback } from 'react';
55
import { IconType } from 'react-icons';
6-
6+
import { BsDot } from 'react-icons/bs';
77
interface SidebarItemProps {
88
label: string;
99
href?: string;
1010
icon: IconType;
1111
onClick?: () => void;
1212
auth?: boolean;
13+
alert?: boolean;
1314
}
1415

15-
function SidebarItem({ label, href, icon: Icon, onClick, auth }: SidebarItemProps) {
16+
function SidebarItem({ label, href, icon: Icon, onClick, auth, alert }: SidebarItemProps) {
1617
const { data: currentUser } = useCurrentUser();
1718
const loginModal = useLoginModal();
1819
const router = useRouter();
@@ -25,14 +26,19 @@ function SidebarItem({ label, href, icon: Icon, onClick, auth }: SidebarItemProp
2526
router.push(href);
2627
}
2728
}, [auth, currentUser, href, loginModal, onClick, router]);
29+
2830
return (
2931
<div onClick={handleClick} className='flex flex-row items-center'>
3032
<div className='relative rounded-full h-14 w-14 flex items-center justify-center p-4 hover:bg-slate-300 hover:bg-opacity-10 cursor-pointer lg:hidden'>
3133
<Icon size={28} color='white' />
34+
{alert ? <BsDot size={70} className='text-sky-500 absolute -top-4 left-0' /> : null}
35+
36+
<p className='hidden lg:block text-white text-xl'>{label}</p>
3237
</div>
3338
<div className='relative hidden lg:flex gap-4 p-4 rounded-full items-center hover:bg-slate-300 hover:bg-opacity-10 cursor-pointer'>
3439
<Icon size={24} color='white' />
3540
<p className='hidden lg:block text-white text-xl'>{label}</p>
41+
{alert ? <BsDot size={70} className='text-sky-500 absolute -top-4 left-0' /> : null}
3642
</div>
3743
</div>
3844
);

‎components/layout/SidebarTweetButton.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import { useRouter } from 'next/router';
33
import { FaFeather } from 'react-icons/fa';
44
import useLoginModal from '@/hooks/useLoginModal';
55
import LoginModal from '../modals/LoginModal';
6+
import useCurrentUser from '@/hooks/useCurrentUser';
67

78
type Props = {};
89

910
function SidebarTweetButton({}: Props) {
1011
const router = useRouter();
1112
const loginModal = useLoginModal();
13+
const { data: currentUser } = useCurrentUser();
1214
const onClick = useCallback(() => {
13-
loginModal.onOpen();
14-
}, []);
15+
if (!currentUser) loginModal.onOpen();
16+
router.push('/');
17+
}, [currentUser, loginModal, router]);
1518

1619
return (
1720
<div onClick={onClick}>

‎hooks/useFollow.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const useFollow = (userId: string) => {
2525
if (isFollowing) {
2626
console.log('unfollow');
2727

28-
request = () => axios.delete('/api/follow', { data: { userId } });
28+
request = () => axios.delete(`/api/follow?userId=${userId}`);
2929
} else {
30-
request = () => axios.post('/api/follow', { userId });
30+
request = () => axios.post(`/api/follow?userId=${userId}`);
3131
}
3232

3333
await request();

‎hooks/useNotifications.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import useSWR from 'swr';
2+
import fetcher from '@/libs/fetcher';
3+
4+
const useNotifications = (userId: string) => {
5+
const url = userId ? `/api/notifications/${userId}` : null;
6+
const { data, isLoading, error, mutate } = useSWR(url, fetcher);
7+
8+
return { data, isLoading, error, mutate };
9+
};
10+
11+
export default useNotifications;

‎pages/api/comments.ts

+22
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
2424
},
2525
});
2626

27+
try {
28+
const post = await prisma.post.findUnique({ where: { id: postId } });
29+
30+
if (post?.userId) {
31+
await prisma.notification.create({
32+
data: {
33+
body: `Someone replied to your tweet`,
34+
userId: post.userId,
35+
},
36+
});
37+
38+
await prisma.user.update({
39+
where: { id: post.userId },
40+
data: {
41+
hasNotification: true,
42+
},
43+
});
44+
}
45+
} catch (error) {
46+
console.log(error);
47+
}
48+
2749
return res.status(200).json(comment);
2850
} catch (error) {
2951
console.log(error);

‎pages/api/like.ts

+21
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
3030

3131
if (req.method === 'POST') {
3232
updatedLikesIds.push(currentUser.id);
33+
try {
34+
const post = await prisma.post.findUnique({ where: { id: postId } });
35+
36+
if (post?.userId) {
37+
await prisma.notification.create({
38+
data: {
39+
body: `Someone liked your tweet`,
40+
userId: post.userId,
41+
},
42+
});
43+
44+
await prisma.user.update({
45+
where: { id: post.userId },
46+
data: {
47+
hasNotification: true,
48+
},
49+
});
50+
}
51+
} catch (error) {
52+
console.log(error);
53+
}
3354
}
3455

3556
if (req.method === 'DELETE') {

‎pages/api/notifications/[userId].ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NextApiRequest, NextApiResponse } from 'next';
2+
import prisma from '@/libs/prismadb';
3+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
4+
if (req.method !== 'GET') {
5+
return res.status(405).end();
6+
}
7+
8+
try {
9+
const { userId } = req.query;
10+
11+
if (!userId || typeof userId !== 'string') {
12+
throw new Error('Invalid ID');
13+
}
14+
15+
const notifications = await prisma.notification.findMany({
16+
where: { userId },
17+
orderBy: { createdAt: 'desc' },
18+
});
19+
20+
await prisma.user.update({
21+
where: { id: userId },
22+
data: { hasNotification: false },
23+
});
24+
25+
return res.status(200).json(notifications);
26+
} catch (error) {
27+
console.log(error);
28+
return res.status(400).end();
29+
}
30+
}

‎pages/notifications.tsx

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Header from '@/components/Header';
2+
import { NextPageContext } from 'next';
3+
import React from 'react';
4+
import { getSession } from 'next-auth/react';
5+
import NotificationsFeed from '@/components/NotificationsFeed';
6+
7+
type Props = {};
8+
9+
export async function getServerSideProps(context: NextPageContext) {
10+
const session = await getSession(context);
11+
if (!session) {
12+
return {
13+
redirect: {
14+
destination: '/',
15+
permanent: false,
16+
},
17+
};
18+
}
19+
return {
20+
props: {
21+
session,
22+
},
23+
};
24+
}
25+
26+
function Notifications({}: Props) {
27+
return (
28+
<>
29+
<Header label='Notifications' showBackArrow />
30+
<NotificationsFeed />
31+
</>
32+
);
33+
}
34+
35+
export default Notifications;

1 commit comments

Comments
 (1)
Please sign in to comment.