You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

157 lines
5.6 KiB

import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Clock, Hash, Server } from 'lucide-react'
import { NostrEvent } from 'nostr-tools'
import { formatDistanceToNow } from 'date-fns'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { truncateText } from '@/lib/utils'
import { DISCUSSION_TOPICS } from './CreateThreadDialog'
import Username from '@/components/Username'
import UserAvatar from '@/components/UserAvatar'
import VoteButtons from '@/components/NoteStats/VoteButtons'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
interface ThreadWithRelaySource extends NostrEvent {
_relaySource?: string
}
interface ThreadCardProps {
thread: ThreadWithRelaySource
onThreadClick: () => void
className?: string
}
export default function ThreadCard({ thread, onThreadClick, className }: ThreadCardProps) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
// Extract title from tags
const titleTag = thread.tags.find(tag => tag[0] === 'title' && tag[1])
const title = titleTag?.[1] || t('Untitled')
// Extract topic from tags
const topicTag = thread.tags.find(tag => tag[0] === 't' && tag[1])
const topic = topicTag?.[1] || 'general'
// Get first 250 words of content
const contentPreview = truncateText(thread.content, 250)
// Format creation time
const createdAt = new Date(thread.created_at * 1000)
const timeAgo = formatDistanceToNow(createdAt, { addSuffix: true })
// Get topic display info from centralized DISCUSSION_TOPICS
const getTopicInfo = (topicId: string) => {
const topic = DISCUSSION_TOPICS.find(t => t.id === topicId)
return topic || {
id: topicId,
label: topicId,
icon: Hash
}
}
const topicInfo = getTopicInfo(topic)
// Format relay name for display
const formatRelayName = (relaySource: string) => {
if (relaySource === 'multiple') {
return t('Multiple Relays')
}
return relaySource.replace('wss://', '').replace('ws://', '')
}
return (
<Card
className={cn(
'clickable hover:shadow-md transition-shadow cursor-pointer',
className
)}
onClick={onThreadClick}
>
<CardHeader className="pb-3">
{isSmallScreen ? (
<div className="space-y-3">
<div className="flex items-start gap-3">
<VoteButtons event={thread} />
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-lg leading-tight line-clamp-2 mb-2">
{title}
</h3>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Badge variant="secondary" className="text-xs">
<topicInfo.icon className="w-3 h-3 mr-1" />
{topicInfo.label}
</Badge>
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{timeAgo}
</div>
</div>
</div>
<div className="flex flex-col items-end gap-2">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<UserAvatar userId={thread.pubkey} size="xSmall" />
<Username
userId={thread.pubkey}
className="truncate font-medium"
skeletonClassName="h-4 w-20"
/>
</div>
{thread._relaySource && (
<Badge variant="outline" className="text-xs">
<Server className="w-3 h-3 mr-1" />
{formatRelayName(thread._relaySource)}
</Badge>
)}
</div>
</div>
</div>
) : (
<div className="flex items-start justify-between gap-3">
<div className="flex items-start gap-3 flex-1 min-w-0">
<VoteButtons event={thread} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h3 className="font-semibold text-lg leading-tight line-clamp-2">
{title}
</h3>
{thread._relaySource && (
<Badge variant="outline" className="text-xs shrink-0">
<Server className="w-3 h-3 mr-1" />
{formatRelayName(thread._relaySource)}
</Badge>
)}
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Badge variant="secondary" className="text-xs">
<topicInfo.icon className="w-3 h-3 mr-1" />
{topicInfo.label}
</Badge>
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{timeAgo}
</div>
</div>
</div>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground shrink-0">
<UserAvatar userId={thread.pubkey} size="xSmall" />
<Username
userId={thread.pubkey}
className="truncate font-medium"
skeletonClassName="h-4 w-20"
/>
</div>
</div>
)}
</CardHeader>
<CardContent className="pt-0">
<div className="text-sm text-muted-foreground leading-relaxed">
{contentPreview}
</div>
</CardContent>
</Card>
)
}