Skip to content

Commit

Permalink
[Feat] Add Notification backend and fetch the data in front-end
Browse files Browse the repository at this point in the history
  • Loading branch information
zakarm committed May 6, 2024
1 parent 066eb9c commit d1aa538
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 30 deletions.
3 changes: 2 additions & 1 deletion app/back-end/dashboards/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib import admin
from .models import Friendship
from .models import Friendship, Notification

# Register your models here.
admin.site.register(Friendship)
admin.site.register(Notification)
12 changes: 12 additions & 0 deletions app/back-end/dashboards/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ class Friendship(models.Model):
u_two_is_blocked_u_one = models.BooleanField(default=False)
class Meta:
db_table = 'Friendship'

class Notification(models.Model):
notification_id = models.AutoField(primary_key=True)
user = models.ForeignKey('authentication.User', models.DO_NOTHING,
db_column='user')
image_url = models.CharField(max_length=200, blank=True)
message_url = models.CharField(max_length=200, blank=True)
title = models.CharField(max_length=50, blank=True)
link = models.CharField(max_length=200, blank=True)

class Meta:
db_table = 'Notification'
23 changes: 20 additions & 3 deletions app/back-end/dashboards/serializer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import serializers
from authentication.models import User
from game.models import Match
from .models import Friendship
from .models import Friendship, Notification
from django.db.models import F, Q
from .utils import (get_total_games,
get_win_games,
Expand Down Expand Up @@ -129,7 +129,6 @@ def get_blocked(self, obj):
def get_is_user_from(self, obj):
return obj.user_from.id == self.context['id']


class BlockedFriendsSerializer(serializers.ModelSerializer):
friends = serializers.SerializerMethodField()
class Meta:
Expand All @@ -138,5 +137,23 @@ class Meta:

def get_friends(self, obj):
friends_data = Friendship.objects.filter(Q(user_from = obj)| Q(user_to= obj))
serializer = BlockedFriendshipSerializer(friends_data, many=True, context = {'id': obj.id})
serializer = BlockedFriendshipSerializer(friends_data, many=True,
context = {'id': obj.id})
return serializer.data


class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = ('notification_id', 'image_url', 'message_url', 'title', 'link')

class NotificationUserSerializer(serializers.ModelSerializer):
notifications = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('id', 'username', 'notifications')

def get_notifications(self, obj):
notifications_data = Notification.objects.filter(user = obj)
serializer = NotificationSerializer(notifications_data, many = True)
return serializer.data
5 changes: 3 additions & 2 deletions app/back-end/dashboards/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
AddFriendshipView,
BlockFriendshipView,
UnblockFriendshipView,
BlockedFriendsView
BlockedFriendsView,
NotificationsView
)

urlpatterns = [
Expand All @@ -24,5 +25,5 @@
path('friends-block', BlockFriendshipView.as_view(), name="friends-block"),
path('friends-unblock', UnblockFriendshipView.as_view(), name="friends-unblock"),
path('blocked-friends', BlockedFriendsView.as_view(), name="friends-unblock"),

path('notifications', NotificationsView.as_view(), name="notifications"),
]
12 changes: 11 additions & 1 deletion app/back-end/dashboards/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
ProfileSerializer,
FriendsSerializer,
UserSerializer,
BlockedFriendsSerializer)
BlockedFriendsSerializer,
NotificationUserSerializer)
from .models import Friendship
from authentication.models import User
from rest_framework import status
Expand Down Expand Up @@ -208,3 +209,12 @@ def get(self, request):
user = request.user
serializer = BlockedFriendsSerializer(instance=user)
return Response(serializer.data)

class NotificationsView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]

def get(self, request):
user = request.user
serializer = NotificationUserSerializer(instance=user)
return Response(serializer.data)
22 changes: 18 additions & 4 deletions app/front-end/src/components/Notification.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import Toast from 'react-bootstrap/Toast';
import Image from 'next/image';

function Notification() {
interface Notif
{
notification_id: number;
image_url: string;
message_url: string;
title: string;
link: string;
}

interface Props
{
notification: Notif;
}

function Notification({notification}: Props) {
return(
<Toast>
<Toast.Header>
<Image src="/char3.png" width={30} height={30} className="rounded me-2" alt="" style={{height: '30px', width: '30px'}}/>
<strong className="me-auto">Bootstrap</strong>
<Image src={notification.image_url} width={30} height={30} className="rounded me-2" alt="" style={{height: '30px', width: '30px'}}/>
<strong className="me-auto">{notification.title}</strong>
<small>11 mins ago</small>
</Toast.Header>
<Toast.Body>Hello, world! This is a toast message.</Toast.Body>
<Toast.Body>{notification.message_url}</Toast.Body>
</Toast>
)
}
Expand Down
52 changes: 49 additions & 3 deletions app/front-end/src/components/mainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ interface User {
is_online: number;
}

interface UserNotification {
id: number;
username: string;
}

interface Notification{
notification_id: number;
image_url: string;
message_url: string;
title: string;
link: string;
}

interface Friend {
user: User;
is_accepted: boolean;
Expand All @@ -50,6 +63,7 @@ export default function MainContainer({ children }: { children: React.ReactNode

const { socket, isLoading } = useGlobalContext();
const [friendsfetched, setFriendsFetched] = useState<FriendSocket[]>([]);
const [notificationFetch, setNotificationFetch] = useState<Notification[]>([]);
const [userData, setuserData] = useState<User>({
id: 0,
username: '',
Expand Down Expand Up @@ -91,6 +105,7 @@ export default function MainContainer({ children }: { children: React.ReactNode
});
if (response.ok){
const data = await response.json();
console.log(data);
const transformedData = data.friends
.filter((friend: Friend) => friend.is_accepted == true)
.map((friend: Friend) => ({
Expand All @@ -113,8 +128,39 @@ export default function MainContainer({ children }: { children: React.ReactNode
}
}
}

fetchRightBarData();
const fetchNotifications = async () =>
{
const access = Cookies.get('access');
if (access){
try {
const response = await fetch('http://localhost:8000/api/notifications',{
headers: { Authorization: `Bearer ${access}`}
});
if (response.ok){
const data = await response.json();
console.log(data);
const notificationFetch = data.notifications
.map((notification: Notification) => ({
notification_id: notification.notification_id,
message_url: notification.message_url,
image_url: notification.image_url,
title: notification.title,
link: notification.link
}));
setNotificationFetch(notificationFetch);
} else if (response.status === 401) {
console.log('Unauthorized');
} else {
console.error('An unexpected error happened:', response.status);
}
}
catch (error){
console.error('An unexpected error happened:', error);
}
}
}
fetchNotifications();
}, []);

return (
Expand Down Expand Up @@ -147,11 +193,11 @@ export default function MainContainer({ children }: { children: React.ReactNode
<div className='col-1 vh-100 d-flex justify-content-end align-items-center text-center' style={{backgroundColor: '#000000'}}>
<div className={`${styles.drag_class} pt-3 pb-3`} style={{backgroundColor: '#161625', borderRadius: '15px 0 0 15px', cursor: 'pointer'}} onClick={toggleShow}>
<FaAngleLeft color="#FFEBEB" size='1.2em'/>
<RightBar userdata={userData} friends_data={friendsfetched} setfriendModal={() => setFriendModal(true)} show={show} setShow={setShow} handleClose={handleClose} toggleShow={toggleShow}/>
<RightBar notifications_data={notificationFetch} userdata={userData} friends_data={friendsfetched} setfriendModal={() => setFriendModal(true)} show={show} setShow={setShow} handleClose={handleClose} toggleShow={toggleShow}/>
</div>
</div>
<div className='col-11'>
<SrightBar friends_data={friendsfetched} setfriendModal={() => setFriendModal(true)} toggleShow={toggleShow}/>
<SrightBar notifications_data={notificationFetch} userdata={userData} friends_data={friendsfetched} setfriendModal={() => setFriendModal(true)} toggleShow={toggleShow}/>
</div>
<InviteFriend show={friendModal} close={() => setFriendModal(false)}/>
</div>
Expand Down
24 changes: 17 additions & 7 deletions app/front-end/src/components/rightBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface User {
interface Props
{
friends_data: any;
notifications_data: any;
userdata: User;
show?: boolean;
setShow: (show: boolean) => void;
Expand All @@ -33,6 +34,14 @@ interface CustomToggleProps {
onClick: () => void;
}

interface Notification{
notification_id: number;
image_url: string;
message_url: string;
title: string;
link: string;
}

interface Friend {
id: number;
username: string;
Expand All @@ -50,7 +59,7 @@ const CustomToggle = forwardRef<HTMLDivElement, CustomToggleProps>(

CustomToggle.displayName = 'CustomToggle';

export default function RightBar({userdata, friends_data, setShow, show, handleClose, toggleShow, setfriendModal} : Props) {
export default function RightBar({notifications_data, userdata, friends_data, setShow, show, handleClose, toggleShow, setfriendModal} : Props) {

const [searchTerm, setSearchTerm] = useState<string>('');
const filteredFriends = friends_data
Expand Down Expand Up @@ -103,17 +112,18 @@ export default function RightBar({userdata, friends_data, setShow, show, handleC
</div>
</Dropdown.Toggle>
<Dropdown.Menu className={`${styles.drop_class}`}>
<Dropdown.Item eventKey="1"><Notification /></Dropdown.Item>
<hr className="dropdown-divider" />
<Dropdown.Item eventKey="2"><Notification /></Dropdown.Item>
<hr className="dropdown-divider" />
<Dropdown.Item eventKey="3"><Notification /></Dropdown.Item>
{notifications_data &&
notifications_data.map((key: Notification, index: number) =>
<Dropdown.Item eventKey={index}><Notification notification={key}/></Dropdown.Item>
// <hr className="dropdown-divider" />
)
}
</Dropdown.Menu>
</Dropdown>
</div>
<div className="row d-flex flex-column text-center">
<div className="col">
<Image className={`${styles.img_class}`} width={60} height={60} src="/char3.png" alt='Profile'/>
<Image className={`${styles.img_class}`} width={60} height={60} src={userdata && userdata.image_url} alt='Profile'/>
</div>
<div className={`col ${styles.profile} mt-2`}>
<h3 className="valo-font">{userdata && userdata.username}</h3>
Expand Down
35 changes: 26 additions & 9 deletions app/front-end/src/components/srightBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { ImUserPlus } from "react-icons/im";
import styles from './styles/srightBar.module.css'
import Splayer from "./Splayer";
import Notification from "./Notification";
import React, { forwardRef } from 'react';
import React, { forwardRef, useEffect } from 'react';
import Image from 'next/image'
import { useRouter } from 'next/navigation';
import Cookies from 'js-cookie';
import { cookies } from 'next/headers';

interface Friend {
id: number;
Expand All @@ -18,8 +19,25 @@ interface Friend {
connected: boolean;
}

interface User {
id: number;
username: string;
image_url: string;
is_online: number;
}

interface Notification{
notification_id: number;
image_url: string;
message_url: string;
title: string;
link: string;
}

interface Props
{
notifications_data: any;
userdata: User;
friends_data : any;
toggleShow: () => void;
setfriendModal: () => void;
Expand All @@ -40,20 +58,17 @@ const CustomToggle = forwardRef<HTMLDivElement, CustomToggleProps>(

CustomToggle.displayName = 'CustomToggle';

export default function SrightBar({toggleShow, setfriendModal, friends_data} : Props) {
export default function SrightBar({notifications_data, userdata, toggleShow, setfriendModal, friends_data} : Props) {

const data = friends_data.sort((usr1: any, usr2: any) => {
if (usr1.connected && !usr2.connected) {
return -1;
}
// Sort disconnected users second
if (!usr1.connected && usr2.connected) {
return 1;
}
// Sort by ID if isConnected flag is the same
return usr1.id - usr2.id;
})
// .slice(0, 5)
.map((user: Friend, index: number) =>
<Splayer key={index} nickname={user.username} id={user.id} image={user.image_url} isConnected={user.connected}/>
);
Expand All @@ -67,14 +82,16 @@ export default function SrightBar({toggleShow, setfriendModal, friends_data} : P
<div className={`${styles.holder} text-center p-2`}>
<div className={`col-inline ${styles.notification1}`}>
<Dropdown>
<Image className={`${styles.img_class1}`} width={60} height={60} src="/char3.png" alt='Profile' onClick={() => router.push('/profile')}/>
<Image className={`${styles.img_class1}`} width={60} height={60} src={userdata.image_url} alt='Profile' onClick={() => router.push('/profile')}/>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components">
<span className={`${styles.badge1}`}>3</span>
</Dropdown.Toggle>
<Dropdown.Menu className="drop-class">
<Dropdown.Item eventKey="1"><Notification /></Dropdown.Item>
<Dropdown.Item eventKey="2"><Notification /></Dropdown.Item>
<Dropdown.Item eventKey="3"><Notification /></Dropdown.Item>
{notifications_data &&
notifications_data.map((key: Notification, index: number) =>
<Dropdown.Item eventKey={index}><Notification notification={key} /></Dropdown.Item>
)
}
</Dropdown.Menu>
</Dropdown>
</div>
Expand Down

0 comments on commit d1aa538

Please sign in to comment.