From d3c338ad68f44fc255e4ea64eac8023f7e1b02ba Mon Sep 17 00:00:00 2001 From: AdeepaK2 Date: Tue, 22 Jul 2025 22:12:42 +0530 Subject: [PATCH 1/2] feat: optimize skill fetching with caching and improve performance in CreateSessionModal --- src/app/api/skillsbyuser/route.ts | 13 +- src/components/messageSystem/SessionBox.tsx | 5 +- .../sessionSystem/CreateSessionModal.tsx | 279 +++++++++++------- 3 files changed, 192 insertions(+), 105 deletions(-) diff --git a/src/app/api/skillsbyuser/route.ts b/src/app/api/skillsbyuser/route.ts index ff76ffe0..f7f45a01 100644 --- a/src/app/api/skillsbyuser/route.ts +++ b/src/app/api/skillsbyuser/route.ts @@ -16,15 +16,24 @@ export async function GET(req: Request) { ); } + // Add select to only fetch necessary fields for better performance const userSkills = await UserSkill.find({ userId }) - .sort({ createdAt: -1 }); + .select('skillTitle proficiencyLevel description categoryName isVerified userId createdAt') + .sort({ createdAt: -1 }) + .lean(); // Use lean() for better performance when we don't need mongoose document methods return NextResponse.json({ success: true, skills: userSkills - }, { status: 200 }); + }, { + status: 200, + headers: { + 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300' // Cache for 1 minute + } + }); } catch (error: any) { + console.error('Error fetching user skills:', error); return NextResponse.json( { success: false, message: error.message }, { status: 500 } diff --git a/src/components/messageSystem/SessionBox.tsx b/src/components/messageSystem/SessionBox.tsx index 408a6612..9d0c281b 100644 --- a/src/components/messageSystem/SessionBox.tsx +++ b/src/components/messageSystem/SessionBox.tsx @@ -543,7 +543,10 @@ export default function SessionBox({ chatRoomId, userId, otherUserId, otherUser: isOpen={showCreateModal} onClose={() => { setShowCreateModal(false); - fetchSessions(); // Refresh sessions when modal closes + // Only fetch sessions if modal was actually used + if (showCreateModal) { + fetchSessions(); // Refresh sessions when modal closes + } }} currentUserId={userId} otherUserId={otherUserId} diff --git a/src/components/sessionSystem/CreateSessionModal.tsx b/src/components/sessionSystem/CreateSessionModal.tsx index caca89da..6671f1db 100644 --- a/src/components/sessionSystem/CreateSessionModal.tsx +++ b/src/components/sessionSystem/CreateSessionModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { X, Calendar, User, BookOpen, CheckCircle, XCircle } from 'lucide-react'; import Alert from '@/components/ui/Alert'; @@ -13,6 +13,10 @@ interface UserSkill { isVerified: boolean; } +// Simple cache for skills to avoid refetching +const skillsCache = new Map(); +const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes + interface CreateSessionModalProps { isOpen: boolean; onClose: () => void; @@ -75,23 +79,78 @@ export default function CreateSessionModal({ const fetchUserSkills = useCallback(async () => { setLoading(true); try { - // Fetch current user skills - const currentUserResponse = await fetch(`/api/skillsbyuser?userId=${currentUserId}`); - const currentUserData = await currentUserResponse.json(); + const now = Date.now(); - // Fetch other user skills - const otherUserResponse = await fetch(`/api/skillsbyuser?userId=${otherUserId}`); - const otherUserData = await otherUserResponse.json(); - - if (currentUserData.success) { - setCurrentUserSkills(currentUserData.skills); + // Check cache first + const currentUserCacheKey = `skills_${currentUserId}`; + const otherUserCacheKey = `skills_${otherUserId}`; + + const currentUserCached = skillsCache.get(currentUserCacheKey); + const otherUserCached = skillsCache.get(otherUserCacheKey); + + // Use cached data if available and not expired + const shouldFetchCurrentUser = !currentUserCached || (now - currentUserCached.timestamp > CACHE_DURATION); + const shouldFetchOtherUser = !otherUserCached || (now - otherUserCached.timestamp > CACHE_DURATION); + + if (!shouldFetchCurrentUser && !shouldFetchOtherUser) { + // Both are cached, use cached data + setCurrentUserSkills(currentUserCached!.skills); + setOtherUserSkills(otherUserCached!.skills); + setLoading(false); + return; + } + + // Prepare fetch promises + const fetchPromises = []; + + if (shouldFetchCurrentUser) { + fetchPromises.push( + fetch(`/api/skillsbyuser?userId=${currentUserId}`) + .then(res => res.json()) + .then(data => ({ type: 'current', data })) + ); + } else { + setCurrentUserSkills(currentUserCached!.skills); + } + + if (shouldFetchOtherUser) { + fetchPromises.push( + fetch(`/api/skillsbyuser?userId=${otherUserId}`) + .then(res => res.json()) + .then(data => ({ type: 'other', data })) + ); + } else { + setOtherUserSkills(otherUserCached!.skills); } - if (otherUserData.success) { - setOtherUserSkills(otherUserData.skills); + // Fetch only what's needed + if (fetchPromises.length > 0) { + const results = await Promise.all(fetchPromises); + + results.forEach((result: any) => { + if (result.data.success) { + if (result.type === 'current') { + setCurrentUserSkills(result.data.skills); + // Cache the result + skillsCache.set(currentUserCacheKey, { + skills: result.data.skills, + timestamp: now + }); + } else if (result.type === 'other') { + setOtherUserSkills(result.data.skills); + // Cache the result + skillsCache.set(otherUserCacheKey, { + skills: result.data.skills, + timestamp: now + }); + } + } + }); } + } catch (error) { console.error('Error fetching skills:', error); + showAlert('error', 'Failed to load skills. Please try again.'); } finally { setLoading(false); } @@ -100,18 +159,25 @@ export default function CreateSessionModal({ // Fetch skills when modal opens useEffect(() => { if (isOpen) { - fetchUserSkills(); - // Set default start date to tomorrow - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - setStartDate(tomorrow.toISOString().split('T')[0]); + // Only fetch if we don't have skills data or user IDs changed + if (currentUserSkills.length === 0 || otherUserSkills.length === 0) { + fetchUserSkills(); + } - // Set default expected end date to 30 days from tomorrow - const defaultEndDate = new Date(); - defaultEndDate.setDate(defaultEndDate.getDate() + 31); - setExpectedEndDate(defaultEndDate.toISOString().split('T')[0]); + // Set default dates only if not already set + if (!startDate) { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + setStartDate(tomorrow.toISOString().split('T')[0]); + } + + if (!expectedEndDate) { + const defaultEndDate = new Date(); + defaultEndDate.setDate(defaultEndDate.getDate() + 31); + setExpectedEndDate(defaultEndDate.toISOString().split('T')[0]); + } } - }, [isOpen, currentUserId, otherUserId, fetchUserSkills]); + }, [isOpen, fetchUserSkills, currentUserSkills.length, otherUserSkills.length, startDate, expectedEndDate]); const validateForm = () => { const newErrors: {[key: string]: string} = {}; @@ -208,6 +274,93 @@ export default function CreateSessionModal({ onClose(); }; + // Memoized skill list components for better performance + const CurrentUserSkillsList = useMemo(() => ( +
+ {currentUserSkills.length === 0 ? ( +

No skills available

+ ) : ( + currentUserSkills.map((skill) => ( +
setSelectedMySkill(skill.id)} + className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer transition-colors ${ + selectedMySkill === skill.id + ? 'border-blue-500 bg-blue-50' + : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' + }`} + > +
+
+ {skill.skillTitle} + {skill.isVerified ? ( + + + + ) : ( + + + + )} +
+
+ {skill.proficiencyLevel} • {skill.categoryName} +
+ {skill.description && ( +
+ {skill.description} +
+ )} +
+
+ )) + )} +
+ ), [currentUserSkills, selectedMySkill]); + + const OtherUserSkillsList = useMemo(() => ( +
+ {otherUserSkills.length === 0 ? ( +

No skills available

+ ) : ( + otherUserSkills.map((skill) => ( +
setSelectedOtherSkill(skill.id)} + className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer transition-colors ${ + selectedOtherSkill === skill.id + ? 'border-blue-500 bg-blue-50' + : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' + }`} + > +
+
+ {skill.skillTitle} + {skill.isVerified ? ( + + + + ) : ( + + + + )} +
+
+ {skill.proficiencyLevel} • {skill.categoryName} +
+ {skill.description && ( +
+ {skill.description} +
+ )} +
+
+ )) + )} +
+ ), [otherUserSkills, selectedOtherSkill]); + if (!isOpen) return null; return ( @@ -263,46 +416,7 @@ export default function CreateSessionModal({ -
- {currentUserSkills.length === 0 ? ( -

No skills available

- ) : ( - currentUserSkills.map((skill) => ( -
setSelectedMySkill(skill.id)} - className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer transition-colors ${ - selectedMySkill === skill.id - ? 'border-blue-500 bg-blue-50' - : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' - }`} - > -
-
- {skill.skillTitle} - {skill.isVerified ? ( - - - - ) : ( - - - - )} -
-
- {skill.proficiencyLevel} • {skill.categoryName} -
- {skill.description && ( -
- {skill.description} -
- )} -
-
- )) - )} -
+ {CurrentUserSkillsList} {errors.mySkill &&

{errors.mySkill}

} @@ -332,46 +446,7 @@ export default function CreateSessionModal({ -
- {otherUserSkills.length === 0 ? ( -

No skills available

- ) : ( - otherUserSkills.map((skill) => ( -
setSelectedOtherSkill(skill.id)} - className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer transition-colors ${ - selectedOtherSkill === skill.id - ? 'border-blue-500 bg-blue-50' - : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' - }`} - > -
-
- {skill.skillTitle} - {skill.isVerified ? ( - - - - ) : ( - - - - )} -
-
- {skill.proficiencyLevel} • {skill.categoryName} -
- {skill.description && ( -
- {skill.description} -
- )} -
-
- )) - )} -
+ {OtherUserSkillsList} {errors.otherSkill &&

{errors.otherSkill}

} From 520fc149b06b1e316433fc723d4255e1a28b8147 Mon Sep 17 00:00:00 2001 From: AdeepaK2 Date: Tue, 22 Jul 2025 22:12:57 +0530 Subject: [PATCH 2/2] fix: update UserSkill interface to use _id and adjust key references in CreateSessionModal --- src/app/api/skillsbyuser/route.ts | 2 +- .../sessionSystem/CreateSessionModal.tsx | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/api/skillsbyuser/route.ts b/src/app/api/skillsbyuser/route.ts index f7f45a01..8bc510a5 100644 --- a/src/app/api/skillsbyuser/route.ts +++ b/src/app/api/skillsbyuser/route.ts @@ -18,7 +18,7 @@ export async function GET(req: Request) { // Add select to only fetch necessary fields for better performance const userSkills = await UserSkill.find({ userId }) - .select('skillTitle proficiencyLevel description categoryName isVerified userId createdAt') + .select('_id skillTitle proficiencyLevel description categoryName isVerified userId createdAt') .sort({ createdAt: -1 }) .lean(); // Use lean() for better performance when we don't need mongoose document methods diff --git a/src/components/sessionSystem/CreateSessionModal.tsx b/src/components/sessionSystem/CreateSessionModal.tsx index 6671f1db..7776f8a0 100644 --- a/src/components/sessionSystem/CreateSessionModal.tsx +++ b/src/components/sessionSystem/CreateSessionModal.tsx @@ -5,7 +5,7 @@ import { X, Calendar, User, BookOpen, CheckCircle, XCircle } from 'lucide-react' import Alert from '@/components/ui/Alert'; interface UserSkill { - id: string; + _id: string; skillTitle: string; proficiencyLevel: string; description: string; @@ -282,10 +282,10 @@ export default function CreateSessionModal({ ) : ( currentUserSkills.map((skill) => (
setSelectedMySkill(skill.id)} + key={skill._id} + onClick={() => setSelectedMySkill(skill._id)} className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer transition-colors ${ - selectedMySkill === skill.id + selectedMySkill === skill._id ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' }`} @@ -325,10 +325,10 @@ export default function CreateSessionModal({ ) : ( otherUserSkills.map((skill) => (
setSelectedOtherSkill(skill.id)} + key={skill._id} + onClick={() => setSelectedOtherSkill(skill._id)} className={`flex items-center justify-between p-3 border rounded-lg cursor-pointer transition-colors ${ - selectedOtherSkill === skill.id + selectedOtherSkill === skill._id ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' }`}