94 lines
4.5 KiB
TypeScript
94 lines
4.5 KiB
TypeScript
import React from 'react';
|
|
import Image from 'next/image';
|
|
import { MaterialDTO, MaterialType, UserDTO } from '../types';
|
|
import { Code, Video, Package, Heart, Download, Eye, Terminal } from 'lucide-react';
|
|
|
|
interface MaterialCardProps {
|
|
material: MaterialDTO;
|
|
onClick: (id: string) => void;
|
|
currentUser?: UserDTO | null;
|
|
}
|
|
|
|
export const MaterialCard: React.FC<MaterialCardProps> = ({ material, onClick, currentUser }) => {
|
|
const Icon = material.type === MaterialType.CODE ? Code : material.type === MaterialType.VIDEO ? Video : Package;
|
|
const typeColor = material.type === MaterialType.CODE ? 'text-cyber-blue' : material.type === MaterialType.VIDEO ? 'text-cyber-pink' : 'text-cyber-neon';
|
|
const borderColor = material.type === MaterialType.CODE ? 'hover:border-cyber-blue/50' : material.type === MaterialType.VIDEO ? 'hover:border-cyber-pink/50' : 'hover:border-cyber-neon/50';
|
|
|
|
// Check if current user has favorited this material
|
|
const isFavorited = currentUser && material.favorites?.some(f => f.userId === currentUser.id);
|
|
const siteIconSvg = encodeURIComponent(
|
|
`<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32'>`+
|
|
`<rect width='100%' height='100%' fill='#0b0b0b'/>`+
|
|
`<circle cx='16' cy='16' r='14' fill='#111' stroke='#39ff14'/>`+
|
|
`<text x='50%' y='56%' dominant-baseline='middle' text-anchor='middle' fill='#39ff14' font-size='12' font-family='monospace'>NM</text>`+
|
|
`</svg>`
|
|
);
|
|
const authorAvatar = material.author.avatarUrl && material.author.avatarUrl.trim()
|
|
? material.author.avatarUrl
|
|
: `data:image/svg+xml;utf8,${siteIconSvg}`;
|
|
|
|
return (
|
|
<div
|
|
onClick={() => onClick(material.id)}
|
|
className={`group relative bg-cyber-panel/40 backdrop-blur-sm border border-white/5 ${borderColor} overflow-hidden cursor-pointer transition-all duration-500 hover:-translate-y-1 hover:shadow-2xl hover:shadow-cyber-neon/5`}
|
|
>
|
|
{/* Decoration Top Right */}
|
|
<div className="absolute top-0 right-0 p-2">
|
|
<Icon className={`w-5 h-5 ${typeColor} opacity-50 group-hover:opacity-100 transition-opacity`} />
|
|
</div>
|
|
|
|
{/* Content Area */}
|
|
<div className="p-6 h-full flex flex-col">
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<div className="text-[10px] font-mono text-gray-500 uppercase tracking-widest border border-gray-800 px-1 rounded">
|
|
{material.type}
|
|
</div>
|
|
{material.language && (
|
|
<div className="text-[10px] font-mono text-cyber-blue uppercase tracking-widest px-1">
|
|
.{material.language}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<h3 className="text-xl font-bold text-gray-100 mb-2 font-mono group-hover:text-cyber-neon transition-colors line-clamp-1">
|
|
{material.title}
|
|
</h3>
|
|
|
|
<p className="text-sm text-gray-400 mb-6 line-clamp-2 flex-grow font-sans">
|
|
{material.description}
|
|
</p>
|
|
|
|
{/* Preview (Code or Image placeholder) */}
|
|
<div className="w-full h-24 bg-black/50 mb-4 rounded border border-white/5 p-2 font-mono text-xs text-gray-600 overflow-hidden relative">
|
|
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-black/80 pointer-events-none"></div>
|
|
{material.type === MaterialType.CODE ? (
|
|
<pre>{material.codeSnippet}</pre>
|
|
) : (
|
|
<div className="flex items-center justify-center h-full text-cyber-pink opacity-50">
|
|
<Terminal size={24} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer Stats */}
|
|
<div className="flex items-center justify-between pt-4 border-t border-white/5 text-xs font-mono text-gray-500">
|
|
<div className="flex items-center gap-3">
|
|
<span className="flex items-center gap-1 hover:text-white"><Eye size={12} /> {material.stats.views}</span>
|
|
<span className={`flex items-center gap-1 ${isFavorited ? 'text-cyber-pink' : 'hover:text-cyber-pink'}`}>
|
|
<Heart size={12} className={isFavorited ? 'fill-cyber-pink' : ''} />
|
|
{material.stats.favorites}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Image src={authorAvatar} alt="author" width={20} height={20} className="w-5 h-5 rounded-full border border-gray-700" />
|
|
<span className="opacity-75">{material.author.username}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Hover Line Animation */}
|
|
<div className="absolute bottom-0 left-0 w-0 h-[2px] bg-cyber-neon group-hover:w-full transition-all duration-500 ease-out"></div>
|
|
</div>
|
|
);
|
|
};
|