refactor(frontend): extract MatchCard component from MatchesPage
- Create components/matches/MatchCard.jsx (119 lines) - Create components/matches/index.js barrel export - Reduce MatchesPage.jsx from 349 → 240 lines (-31%)
This commit is contained in:
118
frontend/src/components/matches/MatchCard.jsx
Normal file
118
frontend/src/components/matches/MatchCard.jsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { MessageCircle, Check, X, Loader2, Calendar, MapPin } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* Match card for matches list page
|
||||
* Shows match details with accept/reject/chat actions
|
||||
*/
|
||||
const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => {
|
||||
const isIncoming = !match.isInitiator && match.status === 'pending';
|
||||
const isOutgoing = match.isInitiator && match.status === 'pending';
|
||||
const isAccepted = match.status === 'accepted';
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Link to={`/${match.partner.username}`} className="flex-shrink-0">
|
||||
<img
|
||||
src={match.partner.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${match.partner.username}`}
|
||||
alt={match.partner.username}
|
||||
className="w-12 h-12 rounded-full hover:ring-2 hover:ring-primary-500 transition-all"
|
||||
/>
|
||||
</Link>
|
||||
<div>
|
||||
<Link to={`/${match.partner.username}`}>
|
||||
<h3 className="font-semibold text-gray-900 hover:text-primary-600 transition-colors">
|
||||
{match.partner.firstName && match.partner.lastName
|
||||
? `${match.partner.firstName} ${match.partner.lastName}`
|
||||
: match.partner.username}
|
||||
</h3>
|
||||
</Link>
|
||||
<p className="text-sm text-gray-600">@{match.partner.username}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-sm text-gray-600 mb-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>{match.event.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>{match.event.location}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{isIncoming && (
|
||||
<span className="text-xs px-2 py-1 bg-amber-100 text-amber-700 rounded-full font-medium">
|
||||
Incoming Request
|
||||
</span>
|
||||
)}
|
||||
{isOutgoing && (
|
||||
<span className="text-xs px-2 py-1 bg-blue-100 text-blue-700 rounded-full font-medium">
|
||||
Sent Request
|
||||
</span>
|
||||
)}
|
||||
{isAccepted && (
|
||||
<span className="text-xs px-2 py-1 bg-green-100 text-green-700 rounded-full font-medium">
|
||||
Active Match
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
{isIncoming && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => onAccept(match.slug)}
|
||||
disabled={processing}
|
||||
className="p-2 bg-green-600 text-white rounded-full hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
title="Accept"
|
||||
>
|
||||
{processing ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<Check className="w-5 h-5" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onReject(match.slug)}
|
||||
disabled={processing}
|
||||
className="p-2 bg-red-600 text-white rounded-full hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
title="Reject"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isOutgoing && (
|
||||
<button
|
||||
onClick={() => onReject(match.slug)}
|
||||
disabled={processing}
|
||||
className="px-3 py-1.5 text-sm border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
Cancel Request
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isAccepted && (
|
||||
<button
|
||||
onClick={() => onOpenChat(match)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700 transition-colors"
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
Open Chat
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchCard;
|
||||
1
frontend/src/components/matches/index.js
Normal file
1
frontend/src/components/matches/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as MatchCard } from './MatchCard';
|
||||
Reference in New Issue
Block a user