Files
spotlightcam/frontend/src/components/dashboard/DashboardMatchCard.jsx

122 lines
4.5 KiB
React
Raw Normal View History

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;