2023-03-09 12:34:08 +00:00
|
|
|
import { clsx } from 'clsx'
|
|
|
|
import styles from './CommentRatingControl.module.scss'
|
|
|
|
import type { Reaction } from '../../graphql/types.gen'
|
|
|
|
import { ReactionKind } from '../../graphql/types.gen'
|
|
|
|
import { useSession } from '../../context/session'
|
|
|
|
import { useReactions } from '../../context/reactions'
|
2023-03-09 23:39:07 +00:00
|
|
|
import { createMemo } from 'solid-js'
|
2023-03-09 12:34:08 +00:00
|
|
|
import { loadShout } from '../../stores/zine/articles'
|
|
|
|
import { Popup } from '../_shared/Popup'
|
2023-03-09 17:01:39 +00:00
|
|
|
import { useLocalize } from '../../context/localize'
|
|
|
|
import { useSnackbar } from '../../context/snackbar'
|
2023-03-09 23:56:19 +00:00
|
|
|
import { VotersList } from '../_shared/VotersList'
|
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()
|
2023-04-26 02:37:29 +00:00
|
|
|
const { user } = useSession()
|
2023-03-09 17:01:39 +00:00
|
|
|
const {
|
|
|
|
actions: { showSnackbar }
|
|
|
|
} = useSnackbar()
|
2023-03-09 12:34:08 +00:00
|
|
|
const {
|
|
|
|
reactionEntities,
|
|
|
|
actions: { createReaction, deleteReaction, loadReactionsBy }
|
|
|
|
} = useReactions()
|
|
|
|
|
|
|
|
const checkReaction = (reactionKind: ReactionKind) =>
|
|
|
|
Object.values(reactionEntities).some(
|
|
|
|
(r) =>
|
|
|
|
r.kind === reactionKind &&
|
2023-04-26 02:37:29 +00:00
|
|
|
r.createdBy.slug === user()?.slug &&
|
2023-03-09 12:34:08 +00:00
|
|
|
r.shout.id === props.comment.shout.id &&
|
|
|
|
r.replyTo === props.comment.id
|
|
|
|
)
|
|
|
|
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
|
|
|
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
2023-04-26 02:37:29 +00:00
|
|
|
const canVote = createMemo(() => user()?.slug !== props.comment.createdBy.slug)
|
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 &&
|
|
|
|
r.replyTo === props.comment.id
|
|
|
|
)
|
|
|
|
)
|
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 &&
|
2023-04-26 02:37:29 +00:00
|
|
|
r.createdBy.slug === user()?.slug &&
|
2023-03-09 12:34:08 +00:00
|
|
|
r.shout.id === props.comment.shout.id &&
|
|
|
|
r.replyTo === props.comment.id
|
|
|
|
)
|
|
|
|
return deleteReaction(reactionToDelete.id)
|
|
|
|
}
|
|
|
|
|
|
|
|
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({
|
|
|
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
|
|
|
shout: props.comment.shout.id,
|
|
|
|
replyTo: props.comment.id
|
|
|
|
})
|
|
|
|
}
|
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 }
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div class={styles.commentRating}>
|
|
|
|
<button
|
2023-03-09 16:19:28 +00:00
|
|
|
role="button"
|
2023-04-26 02:37:29 +00:00
|
|
|
disabled={!canVote() || !user()}
|
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()
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
<Popup
|
|
|
|
trigger={
|
|
|
|
<div
|
|
|
|
class={clsx(styles.commentRatingValue, {
|
|
|
|
[styles.commentRatingPositive]: props.comment.stat.rating > 0,
|
|
|
|
[styles.commentRatingNegative]: props.comment.stat.rating < 0
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
{props.comment.stat.rating || 0}
|
|
|
|
</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"
|
2023-04-26 02:37:29 +00:00
|
|
|
disabled={!canVote() || !user()}
|
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()
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|