Created
November 3, 2024 16:51
-
-
Save undrcrxwn/6d07983719e5c1dc9e793c5f789bb9f5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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