tahamajs's picture
download
raw
9.98 kB
// src/components/article/ArticleCard.tsx
import { Link } from "react-router-dom";
import { useState } from "react";
import { ArticleMeta } from "./ArticleMeta";
import { ArticleAuthor } from "./ArticleAuthor";
import { ArticleTags } from "./ArticleTags";
import { ArticleActions } from "./ArticleActions";
import { ArticleStats } from "./ArticleStats";
import { motion } from "framer-motion";
interface ArticleCardProps {
article: {
id: number;
title: string;
abstract?: string;
coverImage?: string;
createdAt: string;
updatedAt?: string;
readTime?: number;
views: number;
likes?: number;
likesCount?: number;
comments?: number;
commentsCount?: number;
bookmarks?: number;
shares?: number;
citations?: number;
impactScore?: number;
tags: string[];
author?: {
id: number;
username: string;
fullName?: string;
avatarUrl?: string;
affiliation?: string;
isVerified?: boolean;
} | string;
authorUsername?: string;
status?: string;
featured?: boolean;
trending?: boolean;
};
variant?: "default" | "featured" | "compact" | "horizontal" | "minimal";
showAuthor?: boolean;
showTags?: boolean;
showActions?: boolean;
showStats?: boolean;
className?: string;
onLike?: () => void;
onBookmark?: () => void;
onShare?: () => void;
}
export function ArticleCard({
article,
variant = "default",
showAuthor = true,
showTags = true,
showActions = true,
showStats = false,
className = "",
onLike,
onBookmark,
onShare
}: ArticleCardProps) {
const [imageLoaded, setImageLoaded] = useState(false);
const author = typeof article.author === "string" || !article.author
? {
id: 0,
username: article.authorUsername ?? (typeof article.author === "string" ? article.author : "unknown"),
fullName: article.authorUsername ?? (typeof article.author === "string" ? article.author : "Unknown author"),
}
: article.author;
const likes = article.likes ?? article.likesCount ?? 0;
const comments = article.comments ?? article.commentsCount ?? 0;
if (variant === "compact") {
return (
<div className={`group ${className}`}>
<Link to={`/articles/${article.id}`}>
<div className="flex gap-3">
<div className="w-24 h-24 rounded-xl overflow-hidden flex-shrink-0">
<img
src={article.coverImage || `https://picsum.photos/seed/${article.id}/100/100`}
alt={article.title}
className="w-full h-full object-cover group-hover:scale-110 transition duration-300"
onLoad={() => setImageLoaded(true)}
/>
</div>
<div className="flex-1">
<ArticleMeta
createdAt={article.createdAt}
readTime={article.readTime}
views={article.views}
likes={likes}
comments={comments}
variant="compact"
/>
<h4 className="font-semibold text-gray-900 dark:text-white mt-1 line-clamp-2">
{article.title}
</h4>
{showAuthor && (
<ArticleAuthor author={author} variant="compact" className="mt-2" />
)}
</div>
</div>
</Link>
</div>
);
}
if (variant === "horizontal") {
return (
<div className={`flex gap-4 group ${className}`}>
<Link to={`/articles/${article.id}`} className="flex-shrink-0">
<div className="w-48 h-32 rounded-xl overflow-hidden">
<img
src={article.coverImage || `https://picsum.photos/seed/${article.id}/200/150`}
alt={article.title}
className="w-full h-full object-cover group-hover:scale-105 transition duration-300"
onLoad={() => setImageLoaded(true)}
/>
</div>
</Link>
<div className="flex-1">
<ArticleMeta
createdAt={article.createdAt}
readTime={article.readTime}
views={article.views}
likes={likes}
comments={comments}
/>
<Link to={`/articles/${article.id}`}>
<h3 className="text-xl font-bold text-gray-900 dark:text-white mt-2 mb-2 line-clamp-2 group-hover:text-primary transition">
{article.title}
</h3>
</Link>
<p className="text-gray-600 dark:text-gray-400 line-clamp-2 mb-3">
{article.abstract}
</p>
{showTags && article.tags && (
<ArticleTags tags={article.tags.slice(0, 3)} variant="chips" />
)}
{showAuthor && (
<ArticleAuthor author={author} className="mt-3" />
)}
</div>
</div>
);
}
if (variant === "minimal") {
return (
<Link to={`/articles/${article.id}`} className={`block group ${className}`}>
<div className="flex items-start gap-3 p-3 rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800 transition">
<div className="w-12 h-12 rounded-lg overflow-hidden flex-shrink-0">
<img
src={article.coverImage || `https://picsum.photos/seed/${article.id}/50/50`}
alt=""
className="w-full h-full object-cover"
/>
</div>
<div className="flex-1 min-w-0">
<h4 className="font-medium text-gray-900 dark:text-white line-clamp-1 group-hover:text-primary transition">
{article.title}
</h4>
<div className="flex items-center gap-3 text-xs text-gray-500 mt-1">
<span>{new Date(article.createdAt).toLocaleDateString()}</span>
<span>{article.readTime || 5} min read</span>
</div>
</div>
</div>
</Link>
);
}
if (variant === "featured") {
return (
<div className={`group relative overflow-hidden rounded-2xl ${className}`}>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent z-10" />
<img
src={article.coverImage || `https://picsum.photos/seed/${article.id}/800/500`}
alt={article.title}
className="w-full h-96 object-cover group-hover:scale-105 transition duration-500"
onLoad={() => setImageLoaded(true)}
/>
<div className="absolute bottom-0 left-0 right-0 p-6 z-20">
{article.featured && (
<span className="inline-block px-2 py-1 text-xs font-semibold bg-yellow-500 text-black rounded mb-3">
Featured
</span>
)}
<Link to={`/articles/${article.id}`}>
<h2 className="text-2xl font-bold text-white mb-2 line-clamp-2 hover:text-primary transition">
{article.title}
</h2>
</Link>
<p className="text-white/80 line-clamp-2 mb-3">{article.abstract}</p>
<ArticleMeta
createdAt={article.createdAt}
readTime={article.readTime}
views={article.views}
likes={likes}
comments={comments}
className="text-white/60"
/>
</div>
</div>
);
}
// Default variant
return (
<motion.article
whileHover={{ y: -4 }}
className={`group premium-glass rounded-2xl shadow-sm hover-lift transition-all overflow-hidden ${className}`}
>
<Link to={`/articles/${article.id}`}>
<div className="relative h-48 overflow-hidden">
<img
src={article.coverImage || `https://picsum.photos/seed/${article.id}/400/300`}
alt={article.title}
className="w-full h-full object-cover group-hover:scale-105 transition duration-500"
onLoad={() => setImageLoaded(true)}
/>
{article.trending && (
<div className="absolute top-3 left-3">
<span className="px-2 py-1 text-xs font-semibold bg-gradient-to-r from-orange-500 to-red-500 text-white rounded-full">
Trending
</span>
</div>
)}
</div>
</Link>
<div className="p-5">
<ArticleMeta
createdAt={article.createdAt}
readTime={article.readTime}
views={article.views}
likes={likes}
comments={comments}
variant="compact"
/>
<Link to={`/articles/${article.id}`}>
<h3 className="text-xl font-bold text-gray-900 dark:text-white mt-2 mb-2 line-clamp-2 group-hover:text-primary transition">
{article.title}
</h3>
</Link>
<p className="text-gray-600 dark:text-gray-400 text-sm line-clamp-2 mb-3">
{article.abstract}
</p>
{showTags && article.tags && (
<ArticleTags tags={article.tags.slice(0, 3)} variant="chips" className="mb-3" />
)}
<div className="flex items-center justify-between">
{showAuthor && (
<ArticleAuthor author={author as any} variant="compact" />
)}
{showActions && (
<ArticleActions
articleId={article.id}
likesCount={likes}
onLike={onLike}
onBookmark={onBookmark}
onShare={onShare}
variant="horizontal"
showCounts={false}
/>
)}
</div>
{showStats && (
<ArticleStats
views={article.views}
likes={likes}
comments={comments}
bookmarks={article.bookmarks || 0}
shares={article.shares || 0}
citations={article.citations || 0}
variant="minimal"
className="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700"
/>
)}
</div>
</motion.article>
);
}

Xet Storage Details

Size:
9.98 kB
·
Xet hash:
cedcfed1397bcc3eab598a9b945d8fcaa06371d7915aa6807ac086dd59fdf22a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.