Files
sapiens-web3/client/src/components/ArticleCard.tsx
kimjaehyeon0101 3d14ed2c2b Add article thumbnail images to list view and article detail pages
Updated ArticleCard component to display thumbnail images on the right for list view. Modified Article page to display the article's image as a hero banner at the top.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9a264234-c5d7-4dcc-adf3-a954b149b30d
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/9a264234-c5d7-4dcc-adf3-a954b149b30d/6ZMblp5
2025-10-14 07:44:42 +00:00

131 lines
4.4 KiB
TypeScript

import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { useLocation } from "wouter";
import type { Article, MediaOutlet } from "@shared/schema";
interface ArticleCardProps {
article: Article;
outlet: MediaOutlet;
viewMode?: "grid" | "list";
}
export default function ArticleCard({ article, outlet, viewMode = "grid" }: ArticleCardProps) {
const [, setLocation] = useLocation();
const handleClick = () => {
setLocation(`/articles/${article.slug}`);
};
const formatPublishedTime = () => {
if (article.publishedMinutesAgo) {
return `${article.publishedMinutesAgo} min ago`;
}
// Fallback for articles without publishedMinutesAgo
if (article.publishedAt) {
const d = new Date(article.publishedAt);
const now = new Date();
const diffMinutes = Math.floor((now.getTime() - d.getTime()) / (1000 * 60));
const clampedMinutes = Math.max(1, Math.min(diffMinutes, 59));
return `${clampedMinutes} min ago`;
}
return "1 min ago";
};
if (viewMode === "list") {
return (
<Card
className="card-hover cursor-pointer"
onClick={handleClick}
data-testid={`card-article-${article.slug}`}
>
<CardContent className="p-6">
<div className="flex space-x-6">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
{article.isPinned && (
<Badge variant="destructive" className="text-xs">
<i className="fas fa-thumbtack mr-1"></i>
Pinned
</Badge>
)}
{article.isFeatured && (
<Badge variant="default" className="text-xs">
Featured
</Badge>
)}
</div>
<h3 className="text-lg font-semibold mb-2 line-clamp-2">{article.title}</h3>
<p className="text-sm text-muted-foreground mb-3 line-clamp-2">{article.excerpt}</p>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2 text-xs text-muted-foreground">
<span>{formatPublishedTime()}</span>
{article.tags?.map((tag) => (
<Badge key={tag} variant="outline" className="text-xs">
{tag}
</Badge>
))}
</div>
</div>
</div>
{article.imageUrl && (
<img
src={article.imageUrl}
alt={article.title}
className="w-32 h-24 object-cover rounded-lg flex-shrink-0"
data-testid="image-article-thumbnail"
/>
)}
</div>
</CardContent>
</Card>
);
}
return (
<Card
className="card-hover cursor-pointer"
onClick={handleClick}
data-testid={`card-article-${article.slug}`}
>
<CardContent className="p-0">
{article.imageUrl && (
<img
src={article.imageUrl}
alt={article.title}
className="w-full h-48 object-cover rounded-t-xl"
/>
)}
<div className="p-6">
<div className="flex items-center space-x-2 mb-2">
{article.isPinned && (
<Badge variant="destructive" className="text-xs">
<i className="fas fa-thumbtack mr-1"></i>
Pinned
</Badge>
)}
{article.isFeatured && (
<Badge variant="default" className="text-xs">
Featured
</Badge>
)}
</div>
<h3 className="text-lg font-semibold mb-2 line-clamp-2">{article.title}</h3>
<p className="text-sm text-muted-foreground mb-4 line-clamp-3">{article.excerpt}</p>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">
{formatPublishedTime()}
</span>
<div className="flex items-center space-x-1">
{article.tags?.slice(0, 2).map((tag) => (
<Badge key={tag} variant="outline" className="text-xs">
{tag}
</Badge>
))}
</div>
</div>
</div>
</CardContent>
</Card>
);
}