Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 10 additions & 34 deletions src/components/Dashboard/matches/MatchCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import Image from 'next/image';
import { SkillMatch } from '@/types/skillMatch';
import { BadgeCheck, ArrowRightLeft, Eye, MessageCircle, Clock, CheckCircle, XCircle, Award, Calendar, AlertCircle } from 'lucide-react';
import { processAvatarUrl } from '@/utils/avatarUtils';
import { getUserSkillsByUserId } from '@/services/skillServiceAdmin';

interface MatchCardProps {
match: SkillMatch;
onClick: () => void;
currentUserId?: string; // Needed to look up myDetails userId
}

const MatchCard: React.FC<MatchCardProps> = ({ match, onClick }) => {
const MatchCard: React.FC<MatchCardProps> = ({ match, onClick, currentUserId }) => {
const [otherUserKycStatus, setOtherUserKycStatus] = useState<string | null>(null);

// Fetch KYC status for the other user
useEffect(() => {
async function fetchKycStatus() {
Expand Down Expand Up @@ -255,43 +256,18 @@ const MatchCard: React.FC<MatchCardProps> = ({ match, onClick }) => {
: "🔄 Partial match! They can teach you what you're seeking."}
</p>
</div>

{/* Success indicators */}
{match.status === 'accepted' && (
<div className="inline-flex items-center text-xs text-green-700 bg-green-50 border border-green-200 px-2 py-1 rounded-full">
<MessageCircle className="w-3 h-3 mr-1" />
Chat available
</div>
)}
</div>
</div>

{/* Card Footer */}
{/* Card Footer - Centered status only */}
<div className="bg-gradient-to-r from-gray-50 to-gray-100 px-3 py-2 border-t border-gray-100">
<div className="flex items-center justify-between">
{/* Quick action based on status */}
<div className="flex-1 min-w-0">
<div className="text-xs text-gray-600 font-medium truncate">
{match.status === 'pending' && '⏳ Awaiting your response'}
{match.status === 'accepted' && '🚀 Ready to collaborate'}
{match.status === 'completed' && '✅ Successfully completed'}
{match.status === 'rejected' && '❌ Match declined'}
</div>
<div className="flex items-center justify-center">
<div className="text-xs text-gray-600 font-medium truncate text-center w-full">
{match.status === 'pending' && '⏳ Awaiting your response'}
{match.status === 'accepted' && '🚀 Ready to collaborate'}
{match.status === 'completed' && '✅ Successfully completed'}
{match.status === 'rejected' && '❌ Match declined'}
</div>

<button
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"
onClick={(e) => {
e.stopPropagation();
onClick();
}}
>
<Eye className="w-4 h-4 mr-1" />
<span className="hidden sm:inline">
{match.status === 'accepted' ? 'Manage' : 'View Details'}
</span>
<span className="sm:hidden">View</span>
</button>
</div>
</div>
</div>
Expand Down
63 changes: 55 additions & 8 deletions src/components/Dashboard/matches/MatchDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fetchUserChatRooms } from '@/services/chatApiServices';
import { useAuth } from '@/lib/context/AuthContext';
import { BadgeCheck, ArrowRight, MessageCircle, Calendar, XCircle, CheckCircle, Clock, Award, BarChart3, Target, AlertCircle } from 'lucide-react';
import { processAvatarUrl } from '@/utils/avatarUtils';
import { getUserSkillsByUserId } from '@/services/skillServiceAdmin';

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

// Fetch KYC status for the other user
// Skill verification state and effect (must be inside component)
const [mySkillVerified, setMySkillVerified] = useState<boolean | null>(null);
const [otherSkillVerified, setOtherSkillVerified] = useState<boolean | null>(null);
const [myKycStatus, setMyKycStatus] = useState<string | null>(null);
useEffect(() => {
async function fetchSkillVerification() {
// My skill: use currentUserId
if (currentUserId && match.myDetails && match.myDetails.offeringSkill) {
const res = await getUserSkillsByUserId(currentUserId);
if (res.success && res.data) {
const found = res.data.find((s: any) => s.skillTitle === match.myDetails.offeringSkill);
setMySkillVerified(found ? !!found.isVerified : false);
}
}
// Other user's skill
if (match.otherUser && match.otherUser.offeringSkill && match.otherUser.userId) {
const res = await getUserSkillsByUserId(match.otherUser.userId);
if (res.success && res.data) {
const found = res.data.find((s: any) => s.skillTitle === match.otherUser.offeringSkill);
setOtherSkillVerified(found ? !!found.isVerified : false);
}
}
}
fetchSkillVerification();
}, [currentUserId, match.myDetails, match.otherUser]);

// Fetch KYC status for both users
useEffect(() => {
async function fetchKycStatus() {
try {
const res = await fetch(`/api/kyc/status?userId=${match.otherUser.userId}`);
const data = await res.json();
setOtherUserKycStatus(data.success ? data.status : null);
// Fetch other user's KYC status
const otherRes = await fetch(`/api/kyc/status?userId=${match.otherUser.userId}`);
const otherData = await otherRes.json();
setOtherUserKycStatus(otherData.success ? otherData.status : null);

// Fetch my KYC status
if (currentUserId) {
const myRes = await fetch(`/api/kyc/status?userId=${currentUserId}`);
const myData = await myRes.json();
setMyKycStatus(myData.success ? myData.status : null);
}
} catch (err) {
setOtherUserKycStatus(null);
setMyKycStatus(null);
}
}
if (match.otherUser.userId) {
fetchKycStatus();
}
}, [match.otherUser.userId]);
}, [match.otherUser.userId, currentUserId]);

// Format date
const formatDate = (dateString: string) => {
Expand Down Expand Up @@ -363,16 +399,25 @@ const MatchDetailsModal: React.FC<MatchDetailsModalProps> = ({ match, currentUse
/>
</div>
<div>
<h3 className="font-semibold text-blue-800">Your Profile</h3>
<h3 className="font-semibold text-blue-800 flex items-center">
Your Profile
{(myKycStatus === 'Accepted' || myKycStatus === 'Approved') ? (
<BadgeCheck className="w-4 h-4 ml-1 text-blue-500" />
) : (
<AlertCircle className="w-4 h-4 ml-1 text-orange-500" aria-label="Not Verified" />
)}
</h3>
<p className="text-xs text-blue-600">Skills Exchange</p>
</div>
</div>

<div className="space-y-3">
<div className="bg-white p-3 rounded border">
<span className="text-xs font-medium text-green-600 uppercase tracking-wide">Offering</span>
<h4 className="font-semibold text-gray-800 mt-1">
<h4 className="font-semibold text-gray-800 mt-1 flex items-center gap-1">
{match.myDetails.offeringSkill}
{mySkillVerified === true && <BadgeCheck className="w-4 h-4 text-green-500" />}
{mySkillVerified === false && <AlertCircle className="w-4 h-4 text-orange-500" />}
</h4>
<p className="text-sm text-gray-600">
You'll teach this skill
Expand Down Expand Up @@ -441,8 +486,10 @@ const MatchDetailsModal: React.FC<MatchDetailsModalProps> = ({ match, currentUse
<div className="space-y-3">
<div className="bg-white p-3 rounded border">
<span className="text-xs font-medium text-green-600 uppercase tracking-wide">They Offer</span>
<h4 className="font-semibold text-gray-800 mt-1">
<h4 className="font-semibold text-gray-800 mt-1 flex items-center gap-1">
{match.otherUser.offeringSkill}
{otherSkillVerified === true && <BadgeCheck className="w-4 h-4 text-green-500" />}
{otherSkillVerified === false && <AlertCircle className="w-4 h-4 text-orange-500" />}
</h4>
<p className="text-sm text-gray-600">
{match.otherUser.firstName} will teach you this
Expand Down
1 change: 1 addition & 0 deletions src/components/User/DashboardContent/MatchesContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ const MatchesPage = () => {
key={match.id}
match={match}
onClick={() => viewMatchDetails(match)}
currentUserId={currentUserId}
/>
))}
</div>
Expand Down
27 changes: 27 additions & 0 deletions src/services/skillServiceAdmin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Service to fetch another user's skills by userId (for admin or cross-user lookups)
import { ApiResponse, UserSkill } from '@/types/userSkill';

export const getUserSkillsByUserId = async (userId: string): Promise<ApiResponse<UserSkill[]>> => {
try {
const response = await fetch(`/api/userskillfetch?userId=${userId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const apiResponse = await response.json();
if (apiResponse.success && apiResponse.categories) {
// Flatten all skills from all categories
const allSkills = apiResponse.categories.flatMap((cat: any) => cat.skills.map((s: any) => ({
...s,
categoryId: cat.categoryId,
categoryName: cat.categoryName,
})));
return { success: true, data: allSkills };
}
return { success: false, message: apiResponse.message || 'Failed to fetch user skills' };
} catch (error) {
console.error('Error fetching user skills by userId:', error);
return { success: false, message: 'Failed to fetch user skills' };
}
};
Loading