import { Accessor, JSX, createContext, createEffect, createMemo, createSignal, on, useContext } from 'solid-js' import { createStore } from 'solid-js/store' import followMutation from '~/graphql/mutation/core/follow' import unfollowMutation from '~/graphql/mutation/core/unfollow' import loadAuthorFollowers from '../graphql/query/core/author-followers' import { Author, Community, FollowingEntity, Topic } from '../graphql/schema/core.gen' import { useGraphQL } from './graphql' import { useSession } from './session' export type FollowsFilter = 'all' | 'authors' | 'topics' | 'communities' type FollowingData = { slug: string; type: 'follow' | 'unfollow' } interface FollowingContextType { loading: Accessor followers: Accessor setFollows: (follows: AuthorFollowsResult) => void following: Accessor changeFollowing: (what: FollowingEntity, slug: string, value: boolean) => void follows: AuthorFollowsResult loadFollows: () => void follow: (what: FollowingEntity, slug: string) => Promise unfollow: (what: FollowingEntity, slug: string) => Promise } const FollowingContext = createContext({} as FollowingContextType) export function useFollowing() { return useContext(FollowingContext) } interface AuthorFollowsResult { authors?: Author[] topics?: Topic[] communities?: Community[] } const EMPTY_SUBSCRIPTIONS: AuthorFollowsResult = { topics: [] as Topic[], authors: [] as Author[], communities: [] as Community[] } const defaultFollowing = { slug: '', type: 'follow' } as FollowingData export const FollowingProvider = (props: { children: JSX.Element }) => { const [loading, setLoading] = createSignal(false) const [followers, setFollowers] = createSignal([] as Author[]) const [follows, setFollows] = createStore(EMPTY_SUBSCRIPTIONS) const { session } = useSession() const authorized = createMemo(() => Boolean(session()?.access_token)) const { query, mutation } = useGraphQL() const fetchData = async () => { setLoading(true) try { if (session()?.access_token) { console.debug('[context.following] fetching subs data...') const result = await query(loadAuthorFollowers, { user: session()?.user?.id }).toPromise() if (result) { setFollows((_: AuthorFollowsResult) => { return { ...EMPTY_SUBSCRIPTIONS, ...result } as AuthorFollowsResult }) } } } catch (error) { console.warn('[context.following] cannot get subs', error) } finally { setLoading(false) } } const [following, setFollowing] = createSignal(defaultFollowing) const follow = async (what: FollowingEntity, slug: string) => { if (!authorized()) return setFollowing({ slug, type: 'follow' }) try { const resp = await mutation(followMutation, { what, slug }).toPromise() const result = resp?.data?.follow if (!result) return setFollows((subs) => { if (result.authors) subs['authors'] = result.authors || [] if (result.topics) subs['topics'] = result.topics || [] return subs }) } catch (error) { console.error(error) } finally { setFollowing(defaultFollowing) } } const unfollow = async (what: FollowingEntity, slug: string) => { if (!authorized()) return setFollowing({ slug: slug, type: 'unfollow' }) try { const resp = await mutation(unfollowMutation, { what, slug }).toPromise() const result = resp?.data?.unfollow if (!result) return setFollows((subs) => { if (result.authors) subs['authors'] = result.authors || [] if (result.topics) subs['topics'] = result.topics || [] return subs }) } catch (error) { console.error(error) } finally { setFollowing(defaultFollowing) } } createEffect( on( () => session?.()?.user?.app_data, (appdata) => { if (appdata) { const { authors, followers, topics } = appdata setFollows({ authors, topics }) setFollowers(followers) if (!authors) fetchData() } } ) ) const changeFollowing = (what: FollowingEntity, slug: string, value = true) => { setFollows((prevFollows: AuthorFollowsResult) => { const updatedFollows = { ...prevFollows } switch (what) { case 'AUTHOR': { if (value) { if (!updatedFollows.authors?.some((author) => author.slug === slug)) { updatedFollows.authors = [...(updatedFollows.authors || []), { slug } as Author] } } else { updatedFollows.authors = updatedFollows.authors?.filter((author) => author.slug !== slug) || [] } break } case 'TOPIC': { if (value) { if (!updatedFollows.topics?.some((topic) => topic.slug === slug)) { updatedFollows.topics = [...(updatedFollows.topics || []), { slug } as Topic] } } else { updatedFollows.topics = updatedFollows.topics?.filter((topic) => topic.slug !== slug) || [] } break } case 'COMMUNITY': { if (value) { if (!updatedFollows.communities?.some((community) => community.slug === slug)) { updatedFollows.communities = [...(updatedFollows.communities || []), { slug } as Community] } } else { updatedFollows.communities = updatedFollows.communities?.filter((community) => community.slug !== slug) || [] } break } } return updatedFollows }) try { if (value) { follow(what, slug) } else { unfollow(what, slug) } } catch (error) { console.error(error) } finally { fetchData() } } const value: FollowingContextType = { loading, follows, setFollows, following, changeFollowing, followers, loadFollows: fetchData, follow, unfollow } return {props.children} }