Skip to content

Commit

Permalink
displaying freelancer online and offline in realtime for employers
Browse files Browse the repository at this point in the history
  • Loading branch information
andrenormanlang committed Dec 6, 2024
1 parent e1897b7 commit 1b360b7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 6 deletions.
44 changes: 40 additions & 4 deletions apps/api/src/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
// src/chat/chat.gateway.ts

import { WebSocketGateway, WebSocketServer, SubscribeMessage, ConnectedSocket, OnGatewayConnection, MessageBody } from '@nestjs/websockets';
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
ConnectedSocket,
OnGatewayConnection,
OnGatewayDisconnect,
MessageBody,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { UnauthorizedException, Inject, Logger } from '@nestjs/common';
import { UnauthorizedException, Logger } from '@nestjs/common';
import { ChatService } from './chat.service';
import { JwtPayload } from '@/auth/interfaces/jwt-payload.interface';
import { RoomsService } from './rooms.service';

@WebSocketGateway({ cors: { origin: '*' } })
export class ChatGateway implements OnGatewayConnection {
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;

Expand Down Expand Up @@ -37,6 +45,7 @@ export class ChatGateway implements OnGatewayConnection {

const payload = this.jwtService.verify<JwtPayload>(token, { secret });
client.data.userId = payload.sub;
client.data.userRole = payload.role; // Store role in client data

// Disconnect any existing connection for the user
const existingSockets = await this.server.fetchSockets();
Expand All @@ -49,13 +58,39 @@ export class ChatGateway implements OnGatewayConnection {
// User joins their own room
client.join(client.data.userId);

this.logger.log(`User joined room: ${client.data.userId}`);
// If user is employer, join 'employers' room
if (client.data.userRole === 'employer') {
client.join('employers');
this.logger.log(`Employer ${client.data.userId} joined 'employers' room.`);
}

// If user is freelancer, notify employers
if (client.data.userRole === 'freelancer') {
// Notify employers that this freelancer is online
this.server.to('employers').emit('userOnline', { userId: payload.sub });
this.logger.log(`Freelancer ${payload.sub} connected and is now online.`);
}

this.logger.log(`User ${payload.sub} connected with role ${payload.role}`);
} catch (err) {
this.logger.error('Token verification failed:', err.message);
client.disconnect();
}
}

async handleDisconnect(client: Socket) {
const userId = client.data.userId;
const userRole = client.data.userRole;

if (userRole === 'freelancer') {
// Notify employers that this freelancer is offline
this.server.to('employers').emit('userOffline', { userId });
this.logger.log(`Freelancer ${userId} disconnected and is now offline.`);
}

this.logger.log(`User ${userId} disconnected.`);
}

@SubscribeMessage('sendMessage')
async handleSendMessage(
@MessageBody()
Expand Down Expand Up @@ -160,3 +195,4 @@ export class ChatGateway implements OnGatewayConnection {
}



8 changes: 6 additions & 2 deletions apps/client/src/components/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// src/components/UserCard.tsx

import React, { useContext } from "react";
import { User } from "../types/types";
import { UserContext } from "../contexts/UserContext";
Expand All @@ -15,6 +17,7 @@ import DefaultImage from "../assets/default-image.webp";

interface UserCardProps {
user: User;
isOnline?: boolean; // Added
unreadCount?: number; // Existing unreadCount prop
unreadCounts?: { [key: string]: number }; // New prop for freelancers
onViewDetails: (user: User, event: React.MouseEvent) => void;
Expand All @@ -25,6 +28,7 @@ interface UserCardProps {

const UserCard: React.FC<UserCardProps> = ({
user,
isOnline = false, // Added with default value
unreadCount = 0,
unreadCounts,
onViewDetails,
Expand Down Expand Up @@ -60,12 +64,12 @@ const UserCard: React.FC<UserCardProps> = ({
<div className="flex items-center gap-2">
<span
className={`inline-block h-3 w-3 rounded-full ${
user.isOnline
isOnline
? "bg-green-500 ring-2 ring-green-300"
: "bg-gray-500 ring-2 ring-gray-300"
}`}
/>
{user.isOnline ? (
{isOnline ? (
<span className="text-white bg-green-600 px-2 py-1 rounded-md font-semibold text-sm">
Online
</span>
Expand Down
31 changes: 31 additions & 0 deletions apps/client/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const HomePage = () => {
const [unreadCounts, setUnreadCounts] = useState<{ [key: string]: number }>(
{}
);
const [onlineFreelancers, setOnlineFreelancers] = useState<Set<string>>(new Set());

const navigate = useNavigate();
const debouncedSearchQuery = useDebounce(searchQuery, 500);
Expand Down Expand Up @@ -159,6 +160,34 @@ const HomePage = () => {
}
}, [socket, userContext.user?.id]);

// Listen for freelancer online/offline events if user is employer
useEffect(() => {
if (socket && userContext.user?.role === 'employer') {
const handleUserOnline = (data: { userId: string }) => {
setOnlineFreelancers(prev => new Set(prev).add(data.userId));
};

const handleUserOffline = (data: { userId: string }) => {
setOnlineFreelancers(prev => {
const newSet = new Set(prev);
newSet.delete(data.userId);
return newSet;
});
};

socket.on('userOnline', handleUserOnline);
socket.on('userOffline', handleUserOffline);

// Optionally, initialize onlineFreelancers by fetching current online freelancers
// This requires backend support to provide current online users

return () => {
socket.off('userOnline', handleUserOnline);
socket.off('userOffline', handleUserOffline);
};
}
}, [socket, userContext.user?.role]);

const handleDeleteAccount = useCallback(
async (userId: string) => {
await deleteUser(userId);
Expand Down Expand Up @@ -235,6 +264,7 @@ const HomePage = () => {
<UserCard
key={user.id}
user={user}
isOnline={onlineFreelancers.has(user.id)} // Pass isOnline prop
unreadCount={unreadCounts[user.id] || 0} // Pass unreadCount for individual users
unreadCounts={unreadCounts} // Pass the entire unreadCounts object
onViewDetails={openViewModal}
Expand All @@ -255,6 +285,7 @@ const HomePage = () => {
openChatOrRoomsModal,
userContext?.user,
unreadCounts,
onlineFreelancers,
]
);

Expand Down

0 comments on commit 1b360b7

Please sign in to comment.