webapp/src/components/Article/CommentRatingControl.tsx

123 lines
3.9 KiB
TypeScript
Raw Normal View History

import { clsx } from 'clsx'
2023-03-09 23:39:07 +00:00
import { createMemo } from 'solid-js'
2024-06-24 17:50:27 +00:00
import { useFeed } from '~/context/feed'
import { useSnackbar } from '~/context/ui'
2023-03-09 17:01:39 +00:00
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
2023-11-28 13:18:25 +00:00
import { Reaction, ReactionKind } from '../../graphql/schema/core.gen'
import { Popup } from '../_shared/Popup'
2023-03-09 23:56:19 +00:00
import { VotersList } from '../_shared/VotersList'
2023-03-09 12:34:08 +00:00
import styles from './CommentRatingControl.module.scss'
2023-03-09 12:34:08 +00:00
type Props = {
comment: Reaction
}
export const CommentRatingControl = (props: Props) => {
2023-03-09 17:01:39 +00:00
const { t } = useLocalize()
2024-06-24 17:50:27 +00:00
const { loadShout } = useFeed()
const { session } = useSession()
const uid = createMemo<number>(() => session()?.user?.app_data?.profile?.id || 0)
2024-02-04 17:40:15 +00:00
const { showSnackbar } = useSnackbar()
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
2023-03-09 12:34:08 +00:00
const checkReaction = (reactionKind: ReactionKind) =>
Object.values(reactionEntities).some(
(r) =>
r.kind === reactionKind &&
2024-06-24 17:50:27 +00:00
r.created_by.id === uid() &&
2023-03-09 12:34:08 +00:00
r.shout.id === props.comment.shout.id &&
2023-11-28 13:18:25 +00:00
r.reply_to === props.comment.id,
2023-03-09 12:34:08 +00:00
)
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
2024-06-24 17:50:27 +00:00
const canVote = createMemo(() => uid() !== props.comment.created_by.id)
2023-03-09 16:19:28 +00:00
2023-03-09 23:39:07 +00:00
const commentRatingReactions = createMemo(() =>
2023-03-09 12:34:08 +00:00
Object.values(reactionEntities).filter(
(r) =>
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
r.shout.id === props.comment.shout.id &&
2023-11-28 13:18:25 +00:00
r.reply_to === props.comment.id,
),
2023-03-09 12:34:08 +00:00
)
2023-03-09 23:39:07 +00:00
2023-03-09 12:34:08 +00:00
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
const reactionToDelete = Object.values(reactionEntities).find(
(r) =>
r.kind === reactionKind &&
2024-06-24 17:50:27 +00:00
r.created_by.id === uid() &&
2023-03-09 12:34:08 +00:00
r.shout.id === props.comment.shout.id &&
2023-11-28 13:18:25 +00:00
r.reply_to === props.comment.id,
2023-03-09 12:34:08 +00:00
)
2024-06-24 17:50:27 +00:00
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
2023-03-09 12:34:08 +00:00
}
const handleRatingChange = async (isUpvote: boolean) => {
2023-03-09 17:01:39 +00:00
try {
if (isUpvoted()) {
await deleteCommentReaction(ReactionKind.Like)
} else if (isDownvoted()) {
await deleteCommentReaction(ReactionKind.Dislike)
} else {
await createReaction({
2024-06-24 17:50:27 +00:00
reaction: {
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.comment.shout.id,
reply_to: props.comment.id,
},
2023-03-09 17:01:39 +00:00
})
}
2023-03-09 23:42:38 +00:00
} catch {
2023-03-09 17:01:39 +00:00
showSnackbar({ type: 'error', body: t('Error') })
2023-03-09 12:34:08 +00:00
}
await loadShout(props.comment.shout.slug)
await loadReactionsBy({
by: { shout: props.comment.shout.slug },
2023-03-09 12:34:08 +00:00
})
}
return (
<div class={styles.commentRating}>
<button
2023-03-09 16:19:28 +00:00
role="button"
2024-06-24 17:50:27 +00:00
disabled={!(canVote() && uid())}
2023-03-09 16:19:28 +00:00
onClick={() => handleRatingChange(true)}
2023-03-09 12:34:08 +00:00
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
[styles.voted]: isUpvoted(),
2023-03-09 12:34:08 +00:00
})}
/>
<Popup
trigger={
<div
class={clsx(styles.commentRatingValue, {
2024-06-24 17:50:27 +00:00
[styles.commentRatingPositive]: (props.comment?.stat?.rating || 0) > 0,
[styles.commentRatingNegative]: (props.comment?.stat?.rating || 0) < 0,
2023-03-09 12:34:08 +00:00
})}
>
2024-06-24 17:50:27 +00:00
{props.comment?.stat?.rating || 0}
2023-03-09 12:34:08 +00:00
</div>
}
variant="tiny"
>
2023-03-09 23:39:07 +00:00
<VotersList
reactions={commentRatingReactions()}
fallbackMessage={t('This comment has not yet been rated')}
/>
2023-03-09 12:34:08 +00:00
</Popup>
<button
2023-03-09 16:19:28 +00:00
role="button"
2024-06-24 17:50:27 +00:00
disabled={!(canVote() && uid())}
2023-03-09 16:19:28 +00:00
onClick={() => handleRatingChange(false)}
2023-03-09 12:34:08 +00:00
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
[styles.voted]: isDownvoted(),
2023-03-09 12:34:08 +00:00
})}
/>
</div>
)
}