Skip to content

Commit 401e91f

Browse files
Merge pull request #359 from Code102SoftwareProject/skilll-amtches-ui-updated
feat: Enhance Matches Page with Filtering, Searching, and Statistics
2 parents 7c3039f + 8b03c19 commit 401e91f

7 files changed

Lines changed: 1064 additions & 293 deletions

File tree

src/app/api/listings/route.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,29 @@ export async function GET(req: Request) {
5959
.sort({ createdAt: -1 }) // Newest first
6060
.limit(100); // Limit to 100 listings for performance
6161

62+
// For user's own listings, update userDetails with current user data to ensure avatars are up-to-date
63+
let processedListings = listings;
64+
65+
if (queryType === 'mine') {
66+
const currentUser = await User.findById(userId).select('firstName lastName avatar');
67+
68+
if (currentUser) {
69+
processedListings = listings.map(listing => {
70+
const listingObj = listing.toObject();
71+
// Update userDetails with current user data for own listings
72+
listingObj.userDetails = {
73+
firstName: currentUser.firstName,
74+
lastName: currentUser.lastName,
75+
avatar: currentUser.avatar
76+
};
77+
return listingObj;
78+
});
79+
}
80+
}
81+
6282
return NextResponse.json({
6383
success: true,
64-
data: listings
84+
data: processedListings
6585
});
6686
} catch (error) {
6787
console.error('Error fetching listings:', error);

src/app/api/matches/route.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { NextRequest, NextResponse } from 'next/server';
55
import jwt from 'jsonwebtoken';
66
import dbConnect from '@/lib/db';
77
import SkillMatch from '@/lib/models/skillMatch';
8+
import User from '@/lib/models/userSchema';
89

910
// Helper function to get user ID from the token
1011
function getUserIdFromToken(req: NextRequest): string | null {
@@ -67,26 +68,53 @@ export async function GET(request: NextRequest) {
6768
const matches = await SkillMatch.find(query)
6869
.sort({ createdAt: -1 });
6970

70-
// Transform matches to identify the current user's perspective
71+
// Get unique user IDs to fetch current avatar data
72+
const allUserIds = new Set<string>();
73+
matches.forEach(match => {
74+
allUserIds.add(match.userOneId);
75+
allUserIds.add(match.userTwoId);
76+
});
77+
78+
// Fetch current user data for all users involved in matches
79+
const currentUserData = await User.find(
80+
{ _id: { $in: Array.from(allUserIds) } },
81+
'firstName lastName avatar'
82+
).lean();
83+
84+
// Create a map for quick lookup
85+
const userDataMap = new Map();
86+
currentUserData.forEach(user => {
87+
userDataMap.set(user._id.toString(), user);
88+
});
89+
90+
// Transform matches to identify the current user's perspective with updated avatars
7191
const transformedMatches = matches.map(match => {
7292
const isUserOne = match.userOneId === userId;
93+
const otherUserId = isUserOne ? match.userTwoId : match.userOneId;
94+
const otherUserData = userDataMap.get(otherUserId) || {};
95+
const currentUserData = userDataMap.get(userId) || {};
7396

7497
return {
7598
id: match.id,
7699
matchPercentage: match.matchPercentage,
77100
matchType: match.matchType,
78101
status: match.status,
79102
createdAt: match.createdAt,
80-
// Current user's data
81-
myDetails: isUserOne ? match.userOneDetails : match.userTwoDetails,
103+
// Current user's data with updated avatar
104+
myDetails: {
105+
...(isUserOne ? match.userOneDetails : match.userTwoDetails),
106+
firstName: currentUserData.firstName || (isUserOne ? match.userOneDetails.firstName : match.userTwoDetails.firstName),
107+
lastName: currentUserData.lastName || (isUserOne ? match.userOneDetails.lastName : match.userTwoDetails.lastName),
108+
avatar: currentUserData.avatar
109+
},
82110
myListingId: isUserOne ? match.listingOneId : match.listingTwoId,
83-
// Other user's data
111+
// Other user's data with updated avatar
84112
otherUser: {
85-
userId: isUserOne ? match.userTwoId : match.userOneId,
113+
userId: otherUserId,
86114
listingId: isUserOne ? match.listingTwoId : match.listingOneId,
87-
firstName: isUserOne ? match.userTwoDetails.firstName : match.userOneDetails.firstName,
88-
lastName: isUserOne ? match.userTwoDetails.lastName : match.userOneDetails.lastName,
89-
avatar: isUserOne ? match.userTwoDetails.avatar : match.userOneDetails.avatar,
115+
firstName: otherUserData.firstName || (isUserOne ? match.userTwoDetails.firstName : match.userOneDetails.firstName),
116+
lastName: otherUserData.lastName || (isUserOne ? match.userTwoDetails.lastName : match.userOneDetails.lastName),
117+
avatar: otherUserData.avatar,
90118
offeringSkill: isUserOne ? match.userTwoDetails.offeringSkill : match.userOneDetails.offeringSkill,
91119
seekingSkill: isUserOne ? match.userTwoDetails.seekingSkill : match.userOneDetails.seekingSkill
92120
}

src/app/user/chat/page.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,45 @@ function ChatPageContent() {
4949
const [preloadProgress, setPreloadProgress] = useState<{ loaded: number; total: number }>({ loaded: 0, total: 0 });
5050
const [forceRefresh, setForceRefresh] = useState<boolean>(false); // Add state to force refresh
5151

52-
// Check if user came from dashboard (via URL param or recent navigation)
52+
// Check if user came from dashboard and handle auto-selection of chat room
5353
useEffect(() => {
5454
const fromDashboard = searchParams.get('from') === 'dashboard';
55+
const roomId = searchParams.get('roomId');
56+
5557
if (fromDashboard) {
5658
setForceRefresh(true);
5759
// Reset the flag after a short delay to avoid affecting subsequent navigations
5860
setTimeout(() => setForceRefresh(false), 1000);
5961
}
60-
}, [searchParams]);
62+
63+
// Auto-select chat room if roomId is provided
64+
if (roomId && userId) {
65+
// Retry mechanism for newly created chat rooms
66+
const trySelectRoom = async (attempts = 0) => {
67+
const maxAttempts = 3;
68+
69+
try {
70+
// Check if the room exists in user's chat rooms
71+
const chatRooms = await fetchUserChatRooms(userId);
72+
const roomExists = chatRooms.some(room => room._id === roomId);
73+
74+
if (roomExists) {
75+
handleChatSelect(roomId);
76+
} else if (attempts < maxAttempts) {
77+
// Room might still be being created, wait and retry
78+
setTimeout(() => trySelectRoom(attempts + 1), 1000);
79+
} else {
80+
console.warn(`Chat room ${roomId} not found after ${maxAttempts} attempts`);
81+
}
82+
} catch (error) {
83+
console.error('Error finding chat room:', error);
84+
}
85+
};
86+
87+
// Small delay to ensure sidebar is loaded, then try to select room
88+
setTimeout(() => trySelectRoom(), 500);
89+
}
90+
}, [searchParams, userId]);
6191

6292
/**
6393
* * Event Handlers

src/components/Dashboard/listings/ListingCard.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Image from 'next/image';
66
import { SkillListing } from '@/types/skillListing';
77
import { BadgeCheck, Edit, Trash2, Eye, Users, Shield, CheckCircle, Clock, XCircle } from 'lucide-react';
88
import { useAuth } from '@/lib/context/AuthContext';
9+
import { processAvatarUrl } from '@/utils/avatarUtils';
910

1011
interface ListingCardProps {
1112
listing: SkillListing & {
@@ -60,6 +61,9 @@ const ListingCard: React.FC<ListingCardProps> = ({ listing, onDelete, onEdit })
6061
const statusConfig = getStatusConfig(listing.status);
6162
const StatusIcon = statusConfig.icon;
6263
const canModify = isOwner && !listing.isUsedInMatches;
64+
65+
// Process avatar URL or provide fallback
66+
const userAvatar = processAvatarUrl(listing.userDetails.avatar) || '/Avatar.png';
6367

6468
return (
6569
<>
@@ -70,11 +74,16 @@ const ListingCard: React.FC<ListingCardProps> = ({ listing, onDelete, onEdit })
7074
<div className="flex items-center flex-1 min-w-0">
7175
<div className="w-10 h-10 rounded-full overflow-hidden bg-gray-200 mr-3 flex-shrink-0">
7276
<Image
73-
src={'/Avatar.png'}
77+
src={userAvatar}
7478
alt={`${listing.userDetails.firstName} ${listing.userDetails.lastName}`}
7579
width={40}
7680
height={40}
7781
className="object-cover w-full h-full"
82+
onError={(e) => {
83+
const target = e.target as HTMLImageElement;
84+
target.src = '/Avatar.png';
85+
target.onerror = null;
86+
}}
7887
/>
7988
</div>
8089
<div className="flex-1 min-w-0">
@@ -202,11 +211,16 @@ const ListingCard: React.FC<ListingCardProps> = ({ listing, onDelete, onEdit })
202211
<div className="flex items-center mb-6">
203212
<div className="w-12 h-12 rounded-full overflow-hidden bg-gray-200 mr-4">
204213
<Image
205-
src={'/Avatar.png'}
214+
src={userAvatar}
206215
alt={`${listing.userDetails.firstName} ${listing.userDetails.lastName}`}
207216
width={48}
208217
height={48}
209218
className="object-cover w-full h-full"
219+
onError={(e) => {
220+
const target = e.target as HTMLImageElement;
221+
target.src = '/Avatar.png';
222+
target.onerror = null;
223+
}}
210224
/>
211225
</div>
212226
<div className="flex-1">

0 commit comments

Comments
 (0)