9 changed files with 296 additions and 11 deletions
@ -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 @@ |
|||||||
|
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