9 changed files with 296 additions and 11 deletions
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
import { cn } from '@/lib/utils' |
||||
import { useNostr } from '@/providers/NostrProvider' |
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider' |
||||
import noteStatsService from '@/services/note-stats.service' |
||||
import { Event } from 'nostr-tools' |
||||
import { useEffect, useState } from 'react' |
||||
import VoteButtons from './VoteButtons' |
||||
import ReplyButton from './ReplyButton' |
||||
import SeenOnButton from './SeenOnButton' |
||||
|
||||
export default function DiscussionNoteStats({ |
||||
event, |
||||
className, |
||||
classNames, |
||||
fetchIfNotExisting = false |
||||
}: { |
||||
event: Event |
||||
className?: string |
||||
classNames?: { |
||||
buttonBar?: string |
||||
} |
||||
fetchIfNotExisting?: boolean |
||||
}) { |
||||
const { isSmallScreen } = useScreenSize() |
||||
const { pubkey } = useNostr() |
||||
const [loading, setLoading] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
if (!fetchIfNotExisting) return |
||||
setLoading(true) |
||||
noteStatsService.fetchNoteStats(event, pubkey).finally(() => setLoading(false)) |
||||
}, [event, fetchIfNotExisting]) |
||||
|
||||
if (isSmallScreen) { |
||||
return ( |
||||
<div className={cn('select-none', className)}> |
||||
<div |
||||
className={cn( |
||||
'flex justify-between items-center h-5 [&_svg]:size-5', |
||||
loading ? 'animate-pulse' : '', |
||||
classNames?.buttonBar |
||||
)} |
||||
onClick={(e) => e.stopPropagation()} |
||||
> |
||||
<ReplyButton event={event} /> |
||||
<VoteButtons event={event} /> |
||||
<SeenOnButton event={event} /> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<div className={cn('select-none', className)}> |
||||
<div className="flex justify-between h-5 [&_svg]:size-4"> |
||||
<div |
||||
className={cn('flex items-center gap-2', loading ? 'animate-pulse' : '')} |
||||
onClick={(e) => e.stopPropagation()} |
||||
> |
||||
<ReplyButton event={event} /> |
||||
</div> |
||||
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}> |
||||
<VoteButtons event={event} /> |
||||
<SeenOnButton event={event} /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
import { Button } from '@/components/ui/button' |
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' |
||||
import { ChevronDown, Clock, TrendingUp, ArrowUpDown } from 'lucide-react' |
||||
import { useTranslation } from 'react-i18next' |
||||
|
||||
export type SortOption = 'newest' | 'oldest' | 'top' | 'controversial' |
||||
|
||||
export default function ThreadSort({ selectedSort, onSortChange }: { selectedSort: SortOption; onSortChange: (sort: SortOption) => void }) { |
||||
const { t } = useTranslation() |
||||
|
||||
const sortOptions = [ |
||||
{ id: 'newest' as SortOption, label: t('Newest'), icon: Clock }, |
||||
{ id: 'oldest' as SortOption, label: t('Oldest'), icon: Clock }, |
||||
{ id: 'top' as SortOption, label: t('Top'), icon: TrendingUp }, |
||||
{ id: 'controversial' as SortOption, label: t('Controversial'), icon: ArrowUpDown }, |
||||
] |
||||
|
||||
const selectedOption = sortOptions.find(option => option.id === selectedSort) || sortOptions[0] |
||||
|
||||
return ( |
||||
<DropdownMenu> |
||||
<DropdownMenuTrigger asChild> |
||||
<Button variant="outline" className="flex items-center gap-2 h-8"> |
||||
<selectedOption.icon className="w-4 h-4" /> |
||||
<span className="text-sm">{selectedOption.label}</span> |
||||
<ChevronDown className="w-4 h-4" /> |
||||
</Button> |
||||
</DropdownMenuTrigger> |
||||
<DropdownMenuContent align="start"> |
||||
{sortOptions.map(option => ( |
||||
<DropdownMenuItem |
||||
key={option.id} |
||||
onClick={() => onSortChange(option.id)} |
||||
className="flex items-center gap-2" |
||||
> |
||||
<option.icon className="w-4 h-4" /> |
||||
<span>{option.label}</span> |
||||
{option.id === selectedSort && ( |
||||
<span className="ml-auto text-primary">✓</span> |
||||
)} |
||||
</DropdownMenuItem> |
||||
))} |
||||
</DropdownMenuContent> |
||||
</DropdownMenu> |
||||
) |
||||
} |
||||
Loading…
Reference in new issue