import type { JSX } from 'solid-js' import { createContext, onCleanup, useContext } from 'solid-js' import { createStore, reconcile } from 'solid-js/store' 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 getReactionsByQuery from '~/graphql/query/core/reactions-load-by' 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 loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise createReaction: (reaction: MutationCreate_ReactionArgs) => Promise updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise deleteReaction: (id: number) => Promise<{ error: string } | null> } const ReactionsContext = createContext({} as ReactionsContextType) export function useReactions() { return useContext(ReactionsContext) } export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionEntities, setReactionEntities] = createStore>({}) const { t } = useLocalize() const { showSnackbar } = useSnackbar() const { query, mutation } = useGraphQL() const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise => { const resp = await query(getReactionsByQuery, opts) const result = resp?.data?.load_reactions_by || [] const newReactionEntities = result.reduce( (acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => { acc[reaction.id] = reaction return acc }, {}, ) setReactionEntities(newReactionEntities) return result } const createReaction = async (input: MutationCreate_ReactionArgs): Promise => { 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 => { 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, ...actions } return {props.children} }