webapp/src/context/reactions.tsx
2024-07-09 12:13:13 +03:00

128 lines
4.5 KiB
TypeScript

import type { JSX } from 'solid-js'
import { createContext, onCleanup, useContext } from 'solid-js'
import { createStore, reconcile } from 'solid-js/store'
import { loadReactions } from '~/graphql/api/public'
import createReactionMutation from '~/graphql/mutation/core/reaction-create'
import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy'
import updateReactionMutation from '~/graphql/mutation/core/reaction-update'
import {
MutationCreate_ReactionArgs,
MutationUpdate_ReactionArgs,
QueryLoad_Reactions_ByArgs,
Reaction,
ReactionKind
} from '~/graphql/schema/core.gen'
import { useGraphQL } from './graphql'
import { useLocalize } from './localize'
import { useSnackbar } from './ui'
type ReactionsContextType = {
reactionEntities: Record<number, Reaction>
reactionsByShout: Record<string, Reaction[]>
loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]>
createReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void>
updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction>
deleteReaction: (id: number) => Promise<{ error: string } | null>
}
const ReactionsContext = createContext<ReactionsContextType>({} as ReactionsContextType)
export function useReactions() {
return useContext(ReactionsContext)
}
export const ReactionsProvider = (props: { children: JSX.Element }) => {
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
const [reactionsByShout, setReactionsByShout] = createStore<Record<number, Reaction[]>>({})
const { t } = useLocalize()
const { showSnackbar } = useSnackbar()
const { mutation } = useGraphQL()
const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise<Reaction[]> => {
const fetcher = await loadReactions(opts)
const result = (await fetcher()) || []
const newReactionsByShout: Record<string, Reaction[]> = {}
const newReactionEntities = result.reduce(
(acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => {
acc[reaction.id] = reaction
if (!newReactionsByShout[reaction.shout.slug]) newReactionsByShout[reaction.shout.slug] = []
newReactionsByShout[reaction.shout.slug].push(reaction)
return acc
},
{}
)
setReactionsByShout(newReactionsByShout)
setReactionEntities(newReactionEntities)
return result
}
const createReaction = async (input: MutationCreate_ReactionArgs): Promise<void> => {
const resp = await mutation(createReactionMutation, input).toPromise()
const { error, reaction } = resp?.data?.create_reaction || {}
if (error) await showSnackbar({ type: 'error', body: t(error) })
if (!reaction) return
const changes = {
[reaction.id]: reaction
}
if ([ReactionKind.Like, ReactionKind.Dislike].includes(reaction.kind)) {
const oppositeReactionKind =
reaction.kind === ReactionKind.Like ? ReactionKind.Dislike : ReactionKind.Like
const oppositeReaction = Object.values(reactionEntities).find(
(r) =>
r.kind === oppositeReactionKind &&
r.created_by.slug === reaction.created_by.slug &&
r.shout.id === reaction.shout.id &&
r.reply_to === reaction.reply_to
)
if (oppositeReaction) {
changes[oppositeReaction.id] = undefined
}
}
setReactionEntities(changes)
}
const deleteReaction = async (
reaction_id: number
): Promise<{ error: string; reaction?: string } | null> => {
if (reaction_id) {
const resp = await mutation(destroyReactionMutation, { reaction_id }).toPromise()
const result = resp?.data?.destroy_reaction
if (!result.error) {
setReactionEntities({
[reaction_id]: undefined
})
}
return result
}
return null
}
const updateReaction = async (input: MutationUpdate_ReactionArgs): Promise<Reaction> => {
const resp = await mutation(updateReactionMutation, input).toPromise()
const result = resp?.data?.update_reaction
if (!result) throw Error('cannot update reaction')
const { error, reaction } = result
if (error) await showSnackbar({ type: 'error', body: t(error) })
if (reaction) setReactionEntities(reaction.id, reaction)
return reaction
}
onCleanup(() => setReactionEntities(reconcile({})))
const actions = {
loadReactionsBy,
createReaction,
updateReaction,
deleteReaction
}
const value: ReactionsContextType = { reactionEntities, reactionsByShout, ...actions }
return <ReactionsContext.Provider value={value}>{props.children}</ReactionsContext.Provider>
}