Add LatestArticles component and integrate it into BlogHomePage

This commit is contained in:
2025-10-05 16:02:11 +02:00
parent beee3af79f
commit f5490f2628
2 changed files with 102 additions and 75 deletions

View File

@@ -0,0 +1,99 @@
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import type { NostrEvent } from '@nostrify/nostrify';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Calendar, Newspaper } from 'lucide-react';
interface LatestArticlesProps {
posts: NostrEvent[];
}
export function LatestArticles({ posts }: LatestArticlesProps) {
return (
<div className="space-y-6">
{/* Section Header */}
<div className="flex items-center gap-3 border-b pb-4">
<Newspaper className="h-8 w-8 text-primary" />
<div>
<h2 className="text-3xl font-bold tracking-tight">Latest Articles</h2>
<p className="text-sm text-muted-foreground mt-1">
Discover the most recent stories from the community
</p>
</div>
</div>
{/* Posts Grid */}
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => {
const title = post.tags.find(([name]) => name === 'title')?.[1] || 'Untitled';
const summary = post.tags.find(([name]) => name === 'summary')?.[1];
const image = post.tags.find(([name]) => name === 'image')?.[1];
const publishedAt = post.tags.find(([name]) => name === 'published_at')?.[1];
const identifier = post.tags.find(([name]) => name === 'd')?.[1] || '';
const hashtags = post.tags
.filter(([name]) => name === 't')
.map(([, value]) => value)
.slice(0, 3);
const date = publishedAt
? new Date(parseInt(publishedAt) * 1000)
: new Date(post.created_at * 1000);
const naddr = nip19.naddrEncode({
kind: 30023,
pubkey: post.pubkey,
identifier,
});
return (
<Link key={post.id} to={`/${naddr}`}>
<Card className="overflow-hidden hover:shadow-lg transition-shadow h-full flex flex-col">
{image && (
<div className="aspect-video overflow-hidden bg-muted">
<img
src={image}
alt={title}
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
/>
</div>
)}
<CardHeader className="flex-1">
<h3 className="text-xl sm:text-2xl font-bold line-clamp-2 mb-2">
{title}
</h3>
{summary && (
<p className="text-muted-foreground text-sm line-clamp-3">
{summary}
</p>
)}
</CardHeader>
<CardContent className="pt-0">
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-3">
<Calendar className="h-3 w-3" />
<time dateTime={date.toISOString()}>
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
{hashtags.length > 0 && (
<div className="flex flex-wrap gap-1">
{hashtags.map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs">
#{tag}
</Badge>
))}
</div>
)}
</CardContent>
</Card>
</Link>
);
})}
</div>
</div>
);
}

View File

@@ -1,12 +1,9 @@
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { useBlogPosts } from '@/hooks/useBlogPosts';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { Badge } from '@/components/ui/badge';
import { Calendar } from 'lucide-react';
import { RelaySelector } from '@/components/RelaySelector';
import { SearchBar } from '@/components/SearchBar';
import { LatestArticles } from '@/components/LatestArticles';
export default function BlogHomePage() {
const { data: posts, isLoading } = useBlogPosts();
@@ -70,77 +67,8 @@ export default function BlogHomePage() {
<SearchBar />
</div>
{/* Posts grid */}
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => {
const title = post.tags.find(([name]) => name === 'title')?.[1] || 'Untitled';
const summary = post.tags.find(([name]) => name === 'summary')?.[1];
const image = post.tags.find(([name]) => name === 'image')?.[1];
const publishedAt = post.tags.find(([name]) => name === 'published_at')?.[1];
const identifier = post.tags.find(([name]) => name === 'd')?.[1] || '';
const hashtags = post.tags
.filter(([name]) => name === 't')
.map(([, value]) => value)
.slice(0, 3);
const date = publishedAt
? new Date(parseInt(publishedAt) * 1000)
: new Date(post.created_at * 1000);
const naddr = nip19.naddrEncode({
kind: 30023,
pubkey: post.pubkey,
identifier,
});
return (
<Link key={post.id} to={`/${naddr}`}>
<Card className="overflow-hidden hover:shadow-lg transition-shadow h-full flex flex-col">
{image && (
<div className="aspect-video overflow-hidden bg-muted">
<img
src={image}
alt={title}
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
/>
</div>
)}
<CardHeader className="flex-1">
<h2 className="text-xl sm:text-2xl font-bold line-clamp-2 mb-2">
{title}
</h2>
{summary && (
<p className="text-muted-foreground text-sm line-clamp-3">
{summary}
</p>
)}
</CardHeader>
<CardContent className="pt-0">
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-3">
<Calendar className="h-3 w-3" />
<time dateTime={date.toISOString()}>
{date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
{hashtags.length > 0 && (
<div className="flex flex-wrap gap-1">
{hashtags.map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs">
#{tag}
</Badge>
))}
</div>
)}
</CardContent>
</Card>
</Link>
);
})}
</div>
{/* Latest Articles */}
<LatestArticles posts={posts} />
</div>
</div>
);