Skip to content

Commit 2c1f993

Browse files
Merge pull request #369 from Code102SoftwareProject/skill-matches-with-verification-signing-mark
Skill matches with verification signing mark
2 parents d7f9a65 + bc147a6 commit 2c1f993

4 files changed

Lines changed: 93 additions & 42 deletions

File tree

src/components/Dashboard/matches/MatchCard.tsx

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import Image from 'next/image';
55
import { SkillMatch } from '@/types/skillMatch';
66
import { BadgeCheck, ArrowRightLeft, Eye, MessageCircle, Clock, CheckCircle, XCircle, Award, Calendar, AlertCircle } from 'lucide-react';
77
import { processAvatarUrl } from '@/utils/avatarUtils';
8+
import { getUserSkillsByUserId } from '@/services/skillServiceAdmin';
89

910
interface MatchCardProps {
1011
match: SkillMatch;
1112
onClick: () => void;
13+
currentUserId?: string; // Needed to look up myDetails userId
1214
}
1315

14-
const MatchCard: React.FC<MatchCardProps> = ({ match, onClick }) => {
16+
const MatchCard: React.FC<MatchCardProps> = ({ match, onClick, currentUserId }) => {
1517
const [otherUserKycStatus, setOtherUserKycStatus] = useState<string | null>(null);
16-
1718
// Fetch KYC status for the other user
1819
useEffect(() => {
1920
async function fetchKycStatus() {
@@ -255,43 +256,18 @@ const MatchCard: React.FC<MatchCardProps> = ({ match, onClick }) => {
255256
: "🔄 Partial match! They can teach you what you're seeking."}
256257
</p>
257258
</div>
258-
259-
{/* Success indicators */}
260-
{match.status === 'accepted' && (
261-
<div className="inline-flex items-center text-xs text-green-700 bg-green-50 border border-green-200 px-2 py-1 rounded-full">
262-
<MessageCircle className="w-3 h-3 mr-1" />
263-
Chat available
264-
</div>
265-
)}
266259
</div>
267260
</div>
268261

269-
{/* Card Footer */}
262+
{/* Card Footer - Centered status only */}
270263
<div className="bg-gradient-to-r from-gray-50 to-gray-100 px-3 py-2 border-t border-gray-100">
271-
<div className="flex items-center justify-between">
272-
{/* Quick action based on status */}
273-
<div className="flex-1 min-w-0">
274-
<div className="text-xs text-gray-600 font-medium truncate">
275-
{match.status === 'pending' && '⏳ Awaiting your response'}
276-
{match.status === 'accepted' && '🚀 Ready to collaborate'}
277-
{match.status === 'completed' && '✅ Successfully completed'}
278-
{match.status === 'rejected' && '❌ Match declined'}
279-
</div>
264+
<div className="flex items-center justify-center">
265+
<div className="text-xs text-gray-600 font-medium truncate text-center w-full">
266+
{match.status === 'pending' && '⏳ Awaiting your response'}
267+
{match.status === 'accepted' && '🚀 Ready to collaborate'}
268+
{match.status === 'completed' && '✅ Successfully completed'}
269+
{match.status === 'rejected' && '❌ Match declined'}
280270
</div>
281-
282-
<button
283-
className="inline-flex items-center text-blue-600 hover:text-blue-800 text-sm font-medium transition-all duration-200 hover:bg-blue-50 px-2 py-1 rounded flex-shrink-0"
284-
onClick={(e) => {
285-
e.stopPropagation();
286-
onClick();
287-
}}
288-
>
289-
<Eye className="w-4 h-4 mr-1" />
290-
<span className="hidden sm:inline">
291-
{match.status === 'accepted' ? 'Manage' : 'View Details'}
292-
</span>
293-
<span className="sm:hidden">View</span>
294-
</button>
295271
</div>
296272
</div>
297273
</div>

src/components/Dashboard/matches/MatchDetailsModal.tsx

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { fetchUserChatRooms } from '@/services/chatApiServices';
1010
import { useAuth } from '@/lib/context/AuthContext';
1111
import { BadgeCheck, ArrowRight, MessageCircle, Calendar, XCircle, CheckCircle, Clock, Award, BarChart3, Target, AlertCircle } from 'lucide-react';
1212
import { processAvatarUrl } from '@/utils/avatarUtils';
13+
import { getUserSkillsByUserId } from '@/services/skillServiceAdmin';
1314

1415
interface MatchDetailsModalProps {
1516
match: SkillMatch;
@@ -94,21 +95,56 @@ const MatchDetailsModal: React.FC<MatchDetailsModalProps> = ({ match, currentUse
9495
const [openingChat, setOpeningChat] = useState(false);
9596
const [otherUserKycStatus, setOtherUserKycStatus] = useState<string | null>(null);
9697

97-
// Fetch KYC status for the other user
98+
// Skill verification state and effect (must be inside component)
99+
const [mySkillVerified, setMySkillVerified] = useState<boolean | null>(null);
100+
const [otherSkillVerified, setOtherSkillVerified] = useState<boolean | null>(null);
101+
const [myKycStatus, setMyKycStatus] = useState<string | null>(null);
102+
useEffect(() => {
103+
async function fetchSkillVerification() {
104+
// My skill: use currentUserId
105+
if (currentUserId && match.myDetails && match.myDetails.offeringSkill) {
106+
const res = await getUserSkillsByUserId(currentUserId);
107+
if (res.success && res.data) {
108+
const found = res.data.find((s: any) => s.skillTitle === match.myDetails.offeringSkill);
109+
setMySkillVerified(found ? !!found.isVerified : false);
110+
}
111+
}
112+
// Other user's skill
113+
if (match.otherUser && match.otherUser.offeringSkill && match.otherUser.userId) {
114+
const res = await getUserSkillsByUserId(match.otherUser.userId);
115+
if (res.success && res.data) {
116+
const found = res.data.find((s: any) => s.skillTitle === match.otherUser.offeringSkill);
117+
setOtherSkillVerified(found ? !!found.isVerified : false);
118+
}
119+
}
120+
}
121+
fetchSkillVerification();
122+
}, [currentUserId, match.myDetails, match.otherUser]);
123+
124+
// Fetch KYC status for both users
98125
useEffect(() => {
99126
async function fetchKycStatus() {
100127
try {
101-
const res = await fetch(`/api/kyc/status?userId=${match.otherUser.userId}`);
102-
const data = await res.json();
103-
setOtherUserKycStatus(data.success ? data.status : null);
128+
// Fetch other user's KYC status
129+
const otherRes = await fetch(`/api/kyc/status?userId=${match.otherUser.userId}`);
130+
const otherData = await otherRes.json();
131+
setOtherUserKycStatus(otherData.success ? otherData.status : null);
132+
133+
// Fetch my KYC status
134+
if (currentUserId) {
135+
const myRes = await fetch(`/api/kyc/status?userId=${currentUserId}`);
136+
const myData = await myRes.json();
137+
setMyKycStatus(myData.success ? myData.status : null);
138+
}
104139
} catch (err) {
105140
setOtherUserKycStatus(null);
141+
setMyKycStatus(null);
106142
}
107143
}
108144
if (match.otherUser.userId) {
109145
fetchKycStatus();
110146
}
111-
}, [match.otherUser.userId]);
147+
}, [match.otherUser.userId, currentUserId]);
112148

113149
// Format date
114150
const formatDate = (dateString: string) => {
@@ -363,16 +399,25 @@ const MatchDetailsModal: React.FC<MatchDetailsModalProps> = ({ match, currentUse
363399
/>
364400
</div>
365401
<div>
366-
<h3 className="font-semibold text-blue-800">Your Profile</h3>
402+
<h3 className="font-semibold text-blue-800 flex items-center">
403+
Your Profile
404+
{(myKycStatus === 'Accepted' || myKycStatus === 'Approved') ? (
405+
<BadgeCheck className="w-4 h-4 ml-1 text-blue-500" />
406+
) : (
407+
<AlertCircle className="w-4 h-4 ml-1 text-orange-500" aria-label="Not Verified" />
408+
)}
409+
</h3>
367410
<p className="text-xs text-blue-600">Skills Exchange</p>
368411
</div>
369412
</div>
370413

371414
<div className="space-y-3">
372415
<div className="bg-white p-3 rounded border">
373416
<span className="text-xs font-medium text-green-600 uppercase tracking-wide">Offering</span>
374-
<h4 className="font-semibold text-gray-800 mt-1">
417+
<h4 className="font-semibold text-gray-800 mt-1 flex items-center gap-1">
375418
{match.myDetails.offeringSkill}
419+
{mySkillVerified === true && <BadgeCheck className="w-4 h-4 text-green-500" />}
420+
{mySkillVerified === false && <AlertCircle className="w-4 h-4 text-orange-500" />}
376421
</h4>
377422
<p className="text-sm text-gray-600">
378423
You'll teach this skill
@@ -441,8 +486,10 @@ const MatchDetailsModal: React.FC<MatchDetailsModalProps> = ({ match, currentUse
441486
<div className="space-y-3">
442487
<div className="bg-white p-3 rounded border">
443488
<span className="text-xs font-medium text-green-600 uppercase tracking-wide">They Offer</span>
444-
<h4 className="font-semibold text-gray-800 mt-1">
489+
<h4 className="font-semibold text-gray-800 mt-1 flex items-center gap-1">
445490
{match.otherUser.offeringSkill}
491+
{otherSkillVerified === true && <BadgeCheck className="w-4 h-4 text-green-500" />}
492+
{otherSkillVerified === false && <AlertCircle className="w-4 h-4 text-orange-500" />}
446493
</h4>
447494
<p className="text-sm text-gray-600">
448495
{match.otherUser.firstName} will teach you this

src/components/User/DashboardContent/MatchesContent.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ const MatchesPage = () => {
527527
key={match.id}
528528
match={match}
529529
onClick={() => viewMatchDetails(match)}
530+
currentUserId={currentUserId}
530531
/>
531532
))}
532533
</div>

src/services/skillServiceAdmin.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Service to fetch another user's skills by userId (for admin or cross-user lookups)
2+
import { ApiResponse, UserSkill } from '@/types/userSkill';
3+
4+
export const getUserSkillsByUserId = async (userId: string): Promise<ApiResponse<UserSkill[]>> => {
5+
try {
6+
const response = await fetch(`/api/userskillfetch?userId=${userId}`, {
7+
method: 'GET',
8+
headers: {
9+
'Content-Type': 'application/json',
10+
},
11+
});
12+
const apiResponse = await response.json();
13+
if (apiResponse.success && apiResponse.categories) {
14+
// Flatten all skills from all categories
15+
const allSkills = apiResponse.categories.flatMap((cat: any) => cat.skills.map((s: any) => ({
16+
...s,
17+
categoryId: cat.categoryId,
18+
categoryName: cat.categoryName,
19+
})));
20+
return { success: true, data: allSkills };
21+
}
22+
return { success: false, message: apiResponse.message || 'Failed to fetch user skills' };
23+
} catch (error) {
24+
console.error('Error fetching user skills by userId:', error);
25+
return { success: false, message: 'Failed to fetch user skills' };
26+
}
27+
};

0 commit comments

Comments
 (0)