Initializes the client-side application with fundamental UI components, including navigation, cards for articles and auctions, and various elements for user interaction and display. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 069d4324-6c40-4355-955e-c714a50de1ea Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/069d4324-6c40-4355-955e-c714a50de1ea/bVdKIaU
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
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 handleClick = () => {
|
|
window.location.href = `/articles/${article.slug}`;
|
|
};
|
|
|
|
const formatDate = (date: string | Date) => {
|
|
const d = new Date(date);
|
|
const now = new Date();
|
|
const diffTime = Math.abs(now.getTime() - d.getTime());
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 1) return "1 day ago";
|
|
if (diffDays < 7) return `${diffDays} days ago`;
|
|
return d.toLocaleDateString();
|
|
};
|
|
|
|
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">
|
|
{article.imageUrl && (
|
|
<img
|
|
src={article.imageUrl}
|
|
alt={article.title}
|
|
className="w-32 h-24 object-cover rounded-lg flex-shrink-0"
|
|
/>
|
|
)}
|
|
<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>{formatDate(article.publishedAt!)}</span>
|
|
{article.tags?.map((tag) => (
|
|
<Badge key={tag} variant="outline" className="text-xs">
|
|
{tag}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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">
|
|
{formatDate(article.publishedAt!)}
|
|
</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>
|
|
);
|
|
}
|