122 lines
4.5 KiB
React
122 lines
4.5 KiB
React
|
|
import { useNavigate, Link } from 'react-router-dom';
|
||
|
|
import { MessageCircle, Video, Star, Check, Clock } from 'lucide-react';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Video exchange status indicator
|
||
|
|
*/
|
||
|
|
const VideoStatus = ({ exchange }) => {
|
||
|
|
const sent = exchange?.sentByMe;
|
||
|
|
const received = exchange?.receivedFromPartner;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="flex items-center gap-1.5">
|
||
|
|
<Video className="w-4 h-4 text-gray-400" />
|
||
|
|
<span className={sent ? 'text-green-600' : 'text-gray-400'}>
|
||
|
|
{sent ? <Check className="w-3 h-3 inline" /> : <Clock className="w-3 h-3 inline" />} Sent
|
||
|
|
</span>
|
||
|
|
<span className="text-gray-300">|</span>
|
||
|
|
<span className={received ? 'text-green-600' : 'text-gray-400'}>
|
||
|
|
{received ? <Check className="w-3 h-3 inline" /> : <Clock className="w-3 h-3 inline" />} Received
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Rating status indicator
|
||
|
|
*/
|
||
|
|
const RatingStatus = ({ ratings }) => {
|
||
|
|
const ratedByMe = ratings?.ratedByMe;
|
||
|
|
const ratedByPartner = ratings?.ratedByPartner;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="flex items-center gap-1.5">
|
||
|
|
<Star className="w-4 h-4 text-gray-400" />
|
||
|
|
<span className={ratedByMe ? 'text-green-600' : 'text-gray-400'}>
|
||
|
|
{ratedByMe ? <Check className="w-3 h-3 inline" /> : <Clock className="w-3 h-3 inline" />} You
|
||
|
|
</span>
|
||
|
|
<span className="text-gray-300">|</span>
|
||
|
|
<span className={ratedByPartner ? 'text-green-600' : 'text-gray-400'}>
|
||
|
|
{ratedByPartner ? <Check className="w-3 h-3 inline" /> : <Clock className="w-3 h-3 inline" />} Partner
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Match card for dashboard - shows active match with chat access
|
||
|
|
*/
|
||
|
|
const DashboardMatchCard = ({ match }) => {
|
||
|
|
const navigate = useNavigate();
|
||
|
|
const { partner, event, videoExchange, ratings, unreadCount } = match;
|
||
|
|
|
||
|
|
// Can rate when video exchange is complete and user hasn't rated yet
|
||
|
|
const canRate = videoExchange?.sentByMe && videoExchange?.receivedFromPartner && !ratings?.ratedByMe;
|
||
|
|
|
||
|
|
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 gap-4">
|
||
|
|
{/* Avatar */}
|
||
|
|
<Link to={`/${partner.username}`} className="flex-shrink-0 relative">
|
||
|
|
<img
|
||
|
|
src={partner.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${partner.username}`}
|
||
|
|
alt={partner.username}
|
||
|
|
className="w-12 h-12 rounded-full hover:ring-2 hover:ring-primary-500 transition-all"
|
||
|
|
/>
|
||
|
|
{unreadCount > 0 && (
|
||
|
|
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center font-semibold">
|
||
|
|
{unreadCount > 9 ? '9+' : unreadCount}
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</Link>
|
||
|
|
|
||
|
|
{/* Content */}
|
||
|
|
<div className="flex-1 min-w-0">
|
||
|
|
<div className="flex items-start justify-between">
|
||
|
|
<div>
|
||
|
|
<Link to={`/${partner.username}`}>
|
||
|
|
<h3 className="font-semibold text-gray-900 hover:text-primary-600 transition-colors">
|
||
|
|
{partner.firstName && partner.lastName
|
||
|
|
? `${partner.firstName} ${partner.lastName}`
|
||
|
|
: partner.username}
|
||
|
|
</h3>
|
||
|
|
</Link>
|
||
|
|
<p className="text-sm text-gray-500">@{partner.username}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p className="text-sm text-gray-600 mt-1">{event.name}</p>
|
||
|
|
|
||
|
|
{/* Status Indicators */}
|
||
|
|
<div className="flex flex-wrap gap-3 mt-3 text-sm">
|
||
|
|
<VideoStatus exchange={videoExchange} />
|
||
|
|
<RatingStatus ratings={ratings} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Actions */}
|
||
|
|
<div className="flex-shrink-0 flex flex-col gap-2">
|
||
|
|
<button
|
||
|
|
onClick={() => navigate(`/matches/${match.slug}/chat`)}
|
||
|
|
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" />
|
||
|
|
Chat
|
||
|
|
</button>
|
||
|
|
{canRate && (
|
||
|
|
<button
|
||
|
|
onClick={() => navigate(`/matches/${match.slug}/rate`)}
|
||
|
|
className="flex items-center gap-2 px-4 py-2 border border-amber-500 text-amber-600 rounded-md hover:bg-amber-50 transition-colors"
|
||
|
|
>
|
||
|
|
<Star className="w-4 h-4" />
|
||
|
|
Rate
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default DashboardMatchCard;
|