Skip to content

Instantly share code, notes, and snippets.

@undrcrxwn
Created November 3, 2024 16:51
Show Gist options
  • Save undrcrxwn/6d07983719e5c1dc9e793c5f789bb9f5 to your computer and use it in GitHub Desktop.
Save undrcrxwn/6d07983719e5c1dc9e793c5f789bb9f5 to your computer and use it in GitHub Desktop.
import {
ContextMenu,
ContextMenuContent,
ContextMenuTrigger
} from '@/components/ui/context-menu.tsx'
import {
Card,
CardContent,
CardFooter,
CardHeader
} from '@/components/ui/card.tsx'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from '@/components/ui/tooltip.tsx'
import { Button } from '@/components/ui/button.tsx'
import { Separator } from '@/components/ui/separator.tsx'
import { InlineAvatars } from '@/components/inline-avatars.tsx'
import { HTMLAttributes, useEffect, useState } from 'react'
import * as typed from 'typed-contracts'
import {
apiV1DiscussionsDiscussionIdGetOk,
apiV1DiscussionsDiscussionIdReactionsPostFx
} from '@/api/client.ts'
import Profile from '@/components/profile.tsx'
import { format, getTimeSince } from '@/lib/utils.ts'
import { toast } from 'sonner'
import { useUnit } from 'effector-react'
import { $availableReactions } from '@/pages/discussion/model.ts'
export interface DiscussionProps extends HTMLAttributes<HTMLDivElement> {
discussion: typed.Get<typeof apiV1DiscussionsDiscussionIdGetOk>
}
const Discussion = (props: DiscussionProps) => {
const { discussion, ...otherProps } = props
const availableReactions = useUnit($availableReactions)
const [committedReactions, setCommittedReactions] = useState<string[]>([])
const [draftReactions, setDraftReactions] = useState<string[]>([])
useEffect(() => {
const reactions = discussion.viewer_reactions as string[]
setCommittedReactions(reactions)
setDraftReactions(reactions)
}, [])
const toggleReaction = async (reaction: string) => {
const newReactions = draftReactions.includes(reaction)
? draftReactions.filter((draftReaction) => draftReaction !== reaction)
: [...draftReactions, reaction].slice(-3)
setDraftReactions(newReactions)
apiV1DiscussionsDiscussionIdReactionsPostFx({
path: { discussionId: discussion.id },
body: newReactions
})
.then(() => {
setCommittedReactions(newReactions)
})
.catch(() => {
setDraftReactions(committedReactions)
toast('Oops...', {
description: (
<p>
Failed to toggle <span className="font-semibold">{reaction}</span>
</p>
)
})
})
}
const [draftReactionCounters, setDraftReactionCounters] = useState<
Record<string, number>
>(discussion.reaction_counters)
useEffect(() => {
const reactionCounters: Record<string, number> = {
...discussion.reaction_counters
}
discussion.viewer_reactions.forEach((reaction) => {
reactionCounters[reaction] -= 1
})
draftReactions.forEach((reaction) => {
reactionCounters[reaction] = 1 + (reactionCounters[reaction] ?? 0)
})
const sortedReactionCounters: Record<string, number> = Object.fromEntries(
Object.entries(reactionCounters)
.filter(([, counter]) => counter > 0)
.sort(([, a], [, b]) => b - a)
)
setDraftReactionCounters(sortedReactionCounters)
}, [draftReactions])
return (
<Card {...otherProps}>
<ContextMenu>
<ContextMenuTrigger>
<CardHeader>
<div className="flex justify-between">
<Profile
profile={{
displayName: discussion.author.display_name,
username: discussion.author.username,
avatarUrl: discussion.author.avatar_url ?? undefined
}}
/>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger className="h-fit cursor-auto">
<p className="text-sm leading-none text-muted-foreground">
{getTimeSince(discussion.created_at)}
</p>
</TooltipTrigger>
<TooltipContent>
<p>{format(discussion.created_at, 'ru-RU')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</CardHeader>
<CardContent>
<p>
{discussion.title} {discussion.description}
</p>
</CardContent>
<CardFooter>
<div className="space-y-5 w-full">
{Object.keys(draftReactionCounters).length > 0 && (
<>
<div className="flex flex-wrap gap-2">
{Object.entries(draftReactionCounters).map(
([reaction, counter]) => (
<Button
key={reaction}
variant={
draftReactions.includes(reaction)
? 'default'
: 'outline'
}
className="rounded-full h-8 pl-2 pr-3 gap-1"
onClick={() => toggleReaction(reaction)}
>
<span className="text-lg">{reaction}</span>
{counter}
</Button>
)
)}
</div>
<Separator />
</>
)}
<Button variant="outline" className="rounded-full pl-2 gap-3">
<InlineAvatars
profiles={[
{
displayName: 'asfdепной ишак',
username: 'undrcasdfrxwn',
avatarUrl: 'sdfsdsfg'
},
{
displayName: 'Степной ишак',
username: 'undrcrxwn',
avatarUrl: 'https://github.com/undrcrxwn.png'
},
{
displayName: 'mere1y',
username: 'mere1y'
}
]}
/>
42 replies
</Button>
</div>
</CardFooter>
</ContextMenuTrigger>
<ContextMenuContent>
<div className="flex gap-[0.158rem] max-w-80 flex-wrap">
{availableReactions.map((reaction) => (
<Button variant="ghost" onClick={() => toggleReaction(reaction)}>
{reaction}
</Button>
))}
</div>
</ContextMenuContent>
</ContextMenu>
</Card>
)
}
export default Discussion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment