webapp/src/components/Article/ShoutRatingControl.tsx

101 lines
3.3 KiB
TypeScript
Raw Normal View History

import { clsx } from 'clsx'
2024-02-04 11:25:21 +00:00
import { Show, createMemo, createSignal } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
2023-11-28 13:18:25 +00:00
import { ReactionKind, Shout } from '../../graphql/schema/core.gen'
import { loadShout } from '../../stores/zine/articles'
import { Icon } from '../_shared/Icon'
import { Popup } from '../_shared/Popup'
2023-03-09 23:56:19 +00:00
import { VotersList } from '../_shared/VotersList'
import styles from './ShoutRatingControl.module.scss'
interface ShoutRatingControlProps {
shout: Shout
class?: string
}
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
2023-03-09 23:39:07 +00:00
const { t } = useLocalize()
2024-02-04 17:40:15 +00:00
const { author, requireAuthentication } = useSession()
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
2024-01-23 14:41:49 +00:00
const [isLoading, setIsLoading] = createSignal(false)
const checkReaction = (reactionKind: ReactionKind) =>
Object.values(reactionEntities).some(
(r) =>
r.kind === reactionKind &&
2024-01-23 14:41:49 +00:00
r.created_by.id === author()?.id &&
r.shout.id === props.shout.id &&
2023-11-28 13:18:25 +00:00
!r.reply_to,
)
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
const shoutRatingReactions = createMemo(() =>
Object.values(reactionEntities).filter(
2024-01-23 14:41:49 +00:00
(r) => ['LIKE', 'DISLIKE'].includes(r.kind) && r.shout.id === props.shout.id && !r.reply_to,
),
)
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
const reactionToDelete = Object.values(reactionEntities).find(
2023-03-08 16:35:13 +00:00
(r) =>
2024-01-23 14:41:49 +00:00
r.kind === reactionKind &&
r.created_by.id === author()?.id &&
2023-03-08 16:35:13 +00:00
r.shout.id === props.shout.id &&
2023-11-28 13:18:25 +00:00
!r.reply_to,
2024-01-23 14:41:49 +00:00
)
return deleteReaction(reactionToDelete.id)
}
const handleRatingChange = async (isUpvote: boolean) => {
requireAuthentication(async () => {
2024-01-23 14:43:26 +00:00
setIsLoading(true)
2024-01-23 14:41:49 +00:00
if (isUpvoted()) {
await deleteShoutReaction(ReactionKind.Like)
} else if (isDownvoted()) {
await deleteShoutReaction(ReactionKind.Dislike)
} else {
2024-01-23 00:25:00 +00:00
await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.shout.id,
})
}
2024-01-23 14:41:49 +00:00
loadShout(props.shout.slug)
loadReactionsBy({
by: { shout: props.shout.slug },
})
2024-01-23 14:43:26 +00:00
setIsLoading(false)
}, 'vote')
}
return (
2023-07-09 18:34:59 +00:00
<div class={clsx(styles.rating, props.class)}>
2024-01-22 23:16:22 +00:00
<button onClick={() => handleRatingChange(false)} disabled={isLoading()}>
2024-01-23 02:18:17 +00:00
<Show when={!isDownvoted()} fallback={<Icon name="rating-control-checked" />}>
2023-07-09 18:34:59 +00:00
<Icon name="rating-control-less" />
</Show>
</button>
<Popup trigger={<span class={styles.ratingValue}>{props.shout.stat.rating}</span>} variant="tiny">
2023-03-09 23:39:07 +00:00
<VotersList
reactions={shoutRatingReactions()}
fallbackMessage={t('This post has not been rated yet')}
/>
</Popup>
2024-01-22 23:16:22 +00:00
<button onClick={() => handleRatingChange(true)} disabled={isLoading()}>
2024-01-23 02:18:17 +00:00
<Show when={!isUpvoted()} fallback={<Icon name="rating-control-checked" />}>
2023-07-09 18:34:59 +00:00
<Icon name="rating-control-more" />
</Show>
</button>
</div>
)
}