From b460a0d9bf66759984c6e107a3acccb29b15439d Mon Sep 17 00:00:00 2001 From: highperfocused Date: Sun, 5 Oct 2025 21:00:21 +0200 Subject: [PATCH] Refactor article rendering by introducing ArticlePreview component across various pages and removing redundant code --- src/components/ArticlePreview.tsx | 104 +++++++++++++++++++++++++++++ src/components/LatestArticles.tsx | 98 ++------------------------- src/components/LatestInHashtag.tsx | 99 ++------------------------- src/pages/ProfilePage.tsx | 76 ++------------------- src/pages/SearchResultsPage.tsx | 74 ++------------------ 5 files changed, 124 insertions(+), 327 deletions(-) create mode 100644 src/components/ArticlePreview.tsx diff --git a/src/components/ArticlePreview.tsx b/src/components/ArticlePreview.tsx new file mode 100644 index 0000000..6e5cd30 --- /dev/null +++ b/src/components/ArticlePreview.tsx @@ -0,0 +1,104 @@ +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 { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Calendar } from 'lucide-react'; +import { useAuthor } from '@/hooks/useAuthor'; +import { genUserName } from '@/lib/genUserName'; + +interface ArticlePreviewProps { + post: NostrEvent; + variant?: 'default' | 'compact'; + showAuthor?: boolean; +} + +export function ArticlePreview({ post, variant = 'default', showAuthor = true }: ArticlePreviewProps) { + const { data: author } = useAuthor(post.pubkey); + const metadata = author?.metadata; + + const title = post.tags.find(([name]: [string]) => name === 'title')?.[1] || 'Untitled'; + const summary = post.tags.find(([name]: [string]) => name === 'summary')?.[1]; + const image = post.tags.find(([name]: [string]) => name === 'image')?.[1]; + const publishedAt = post.tags.find(([name]: [string]) => name === 'published_at')?.[1]; + const identifier = post.tags.find(([name]: [string]) => name === 'd')?.[1] || ''; + const hashtags = post.tags + .filter(([name]: [string]) => name === 't') + .map(([, value]: [string, string]) => value) + .slice(0, 3); + + const date = publishedAt + ? new Date(parseInt(publishedAt) * 1000) + : new Date(post.created_at * 1000); + + const naddr = nip19.naddrEncode({ + kind: post.kind, + pubkey: post.pubkey, + identifier, + }); + + const displayName = metadata?.name || metadata?.display_name || genUserName(post.pubkey); + const avatarUrl = metadata?.picture; + + const isCompact = variant === 'compact'; + const titleSize = isCompact ? 'text-lg' : 'text-xl sm:text-2xl'; + const summaryLines = isCompact ? 'line-clamp-2' : 'line-clamp-3'; + const dateFormat = isCompact + ? { month: 'short', day: 'numeric', year: 'numeric' } + : { year: 'numeric', month: 'long', day: 'numeric' }; + + return ( + + + {image && ( +
+ {title} +
+ )} + +

+ {title} +

+ {summary && ( +

+ {summary} +

+ )} +
+ +
0 ? 'mb-3' : ''}`}> + + +
+ {showAuthor && ( +
0 ? 'mb-3' : ''}`}> + + + + {displayName.slice(0, 2).toUpperCase()} + + + {displayName} +
+ )} + {hashtags.length > 0 && ( +
+ {hashtags.map((tag: string) => ( + + #{tag} + + ))} +
+ )} +
+
+ + ); +} diff --git a/src/components/LatestArticles.tsx b/src/components/LatestArticles.tsx index 82cce6b..a5fc451 100644 --- a/src/components/LatestArticles.tsx +++ b/src/components/LatestArticles.tsx @@ -1,104 +1,14 @@ import { useState } from 'react'; -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 { Card, CardHeader } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Calendar, Newspaper, ChevronDown } from 'lucide-react'; +import { Newspaper, ChevronDown } from 'lucide-react'; import { useBlogPosts } from '@/hooks/useBlogPosts'; -import { useAuthor } from '@/hooks/useAuthor'; -import { genUserName } from '@/lib/genUserName'; +import { ArticlePreview } from '@/components/ArticlePreview'; const INITIAL_POSTS_COUNT = 3; const LOAD_MORE_COUNT = 6; -function ArticleCard({ post }: { post: NostrEvent }) { - const { data: author } = useAuthor(post.pubkey); - const metadata = author?.metadata; - - const title = post.tags.find(([name]: [string]) => name === 'title')?.[1] || 'Untitled'; - const summary = post.tags.find(([name]: [string]) => name === 'summary')?.[1]; - const image = post.tags.find(([name]: [string]) => name === 'image')?.[1]; - const publishedAt = post.tags.find(([name]: [string]) => name === 'published_at')?.[1]; - const identifier = post.tags.find(([name]: [string]) => name === 'd')?.[1] || ''; - const hashtags = post.tags - .filter(([name]: [string]) => name === 't') - .map(([, value]: [string, string]) => 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, - }); - - const displayName = metadata?.name || metadata?.display_name || genUserName(post.pubkey); - const avatarUrl = metadata?.picture; - - return ( - - - {image && ( -
- {title} -
- )} - -

- {title} -

- {summary && ( -

- {summary} -

- )} -
- -
- - -
-
- - - - {displayName.slice(0, 2).toUpperCase()} - - - {displayName} -
- {hashtags.length > 0 && ( -
- {hashtags.map((tag: string) => ( - - #{tag} - - ))} -
- )} -
-
- - ); -} - export function LatestArticles() { const [visibleCount, setVisibleCount] = useState(INITIAL_POSTS_COUNT); const { data: posts, isLoading } = useBlogPosts(); @@ -157,7 +67,7 @@ export function LatestArticles() { {/* Posts Grid */}
{visiblePosts.map((post) => ( - + ))}
diff --git a/src/components/LatestInHashtag.tsx b/src/components/LatestInHashtag.tsx index 223ecff..26dc180 100644 --- a/src/components/LatestInHashtag.tsx +++ b/src/components/LatestInHashtag.tsx @@ -1,15 +1,10 @@ -import { Link, useNavigate } 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 { useNavigate } from 'react-router-dom'; +import { Card, CardHeader } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Calendar, Hash, ChevronRight } from 'lucide-react'; +import { Hash, ChevronRight } from 'lucide-react'; import { useBlogPostsByHashtag } from '@/hooks/useBlogPostsByHashtag'; -import { useAuthor } from '@/hooks/useAuthor'; -import { genUserName } from '@/lib/genUserName'; +import { ArticlePreview } from '@/components/ArticlePreview'; interface LatestInHashtagProps { hashtag: string; @@ -18,90 +13,6 @@ interface LatestInHashtagProps { const INITIAL_POSTS_COUNT = 3; -function HashtagArticleCard({ post }: { post: NostrEvent }) { - const { data: author } = useAuthor(post.pubkey); - const metadata = author?.metadata; - - const title = post.tags.find(([name]: [string]) => name === 'title')?.[1] || 'Untitled'; - const summary = post.tags.find(([name]: [string]) => name === 'summary')?.[1]; - const image = post.tags.find(([name]: [string]) => name === 'image')?.[1]; - const publishedAt = post.tags.find(([name]: [string]) => name === 'published_at')?.[1]; - const identifier = post.tags.find(([name]: [string]) => name === 'd')?.[1] || ''; - const hashtags = post.tags - .filter(([name]: [string]) => name === 't') - .map(([, value]: [string, string]) => 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, - }); - - const displayName = metadata?.name || metadata?.display_name || genUserName(post.pubkey); - const avatarUrl = metadata?.picture; - - return ( - - - {image && ( -
- {title} -
- )} - -

- {title} -

- {summary && ( -

- {summary} -

- )} -
- -
- - -
-
- - - - {displayName.slice(0, 2).toUpperCase()} - - - {displayName} -
- {hashtags.length > 0 && ( -
- {hashtags.map((tag: string) => ( - - #{tag} - - ))} -
- )} -
-
- - ); -} - export function LatestInHashtag({ hashtag, icon }: LatestInHashtagProps) { const navigate = useNavigate(); const { data: posts, isLoading } = useBlogPostsByHashtag(hashtag); @@ -171,7 +82,7 @@ export function LatestInHashtag({ hashtag, icon }: LatestInHashtagProps) { {/* Posts Grid */}
{visiblePosts.map((post) => ( - + ))}
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 808f33a..370b3e9 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -1,4 +1,4 @@ -import { useParams, Link } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { useAuthor } from '@/hooks/useAuthor'; import { useAuthorBlogPosts } from '@/hooks/useAuthorBlogPosts'; @@ -7,9 +7,10 @@ import { Skeleton } from '@/components/ui/skeleton'; import { Badge } from '@/components/ui/badge'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; -import { Calendar, Link2, Mail, Copy, Check } from 'lucide-react'; +import { Link2, Mail, Copy, Check } from 'lucide-react'; import { genUserName } from '@/lib/genUserName'; import { RelaySelector } from '@/components/RelaySelector'; +import { ArticlePreview } from '@/components/ArticlePreview'; import { useToast } from '@/hooks/useToast'; import NotFound from '@/pages/NotFound'; import { useState } from 'react'; @@ -234,74 +235,9 @@ export default function ProfilePage() { ) : posts && posts.length > 0 ? (
- {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 tags = post.tags.filter(([name]) => name === 't').map(([, value]) => value); - const identifier = post.tags.find(([name]) => name === 'd')?.[1]; - - const naddr = nip19.naddrEncode({ - kind: post.kind, - pubkey: post.pubkey, - identifier: identifier || '', - }); - - const date = publishedAt - ? new Date(parseInt(publishedAt) * 1000) - : new Date(post.created_at * 1000); - - return ( - - - {image && ( -
- {title} -
- )} - -

- {title} -

- {summary && ( -

- {summary} -

- )} -
- - -
- {tags.length > 0 && ( -
- {tags.slice(0, 3).map((tag) => ( - - {tag} - - ))} - {tags.length > 3 && ( - - +{tags.length - 3} - - )} -
- )} -
-
- - ); - })} + {posts.map((post) => ( + + ))}
) : ( diff --git a/src/pages/SearchResultsPage.tsx b/src/pages/SearchResultsPage.tsx index 7b14cbb..ea3acfb 100644 --- a/src/pages/SearchResultsPage.tsx +++ b/src/pages/SearchResultsPage.tsx @@ -5,9 +5,10 @@ import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { Badge } from '@/components/ui/badge'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { User, FileText, ArrowLeft, Calendar } from 'lucide-react'; +import { User, FileText, ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { SearchBar } from '@/components/SearchBar'; +import { ArticlePreview } from '@/components/ArticlePreview'; import type { NostrMetadata } from '@nostrify/nostrify'; export default function SearchResultsPage() { @@ -161,74 +162,9 @@ export default function SearchResultsPage() { Articles ({articles.length})
- {articles.map((result) => { - const title = result.event.tags.find(([name]) => name === 'title')?.[1] || 'Untitled'; - const summary = result.event.tags.find(([name]) => name === 'summary')?.[1]; - const image = result.event.tags.find(([name]) => name === 'image')?.[1]; - const publishedAt = result.event.tags.find(([name]) => name === 'published_at')?.[1]; - const identifier = result.event.tags.find(([name]) => name === 'd')?.[1] || ''; - const hashtags = result.event.tags - .filter(([name]) => name === 't') - .map(([, value]) => value) - .slice(0, 3); - - const date = publishedAt - ? new Date(parseInt(publishedAt) * 1000) - : new Date(result.event.created_at * 1000); - - const naddr = nip19.naddrEncode({ - kind: 30023, - pubkey: result.event.pubkey, - identifier, - }); - - return ( - - - {image && ( -
- {title} -
- )} - -

- {title} -

- {summary && ( -

- {summary} -

- )} -
- -
- - -
- {hashtags.length > 0 && ( -
- {hashtags.map((tag) => ( - - #{tag} - - ))} -
- )} -
-
- - ); - })} + {articles.map((result) => ( + + ))}
)}