import { makePersisted } from '@solid-primitives/storage' import type { Accessor, JSX } from 'solid-js' import { createContext, createMemo, createSignal, onMount, useContext } from 'solid-js' import { createStore } from 'solid-js/store' import { Portal } from 'solid-js/web' import markSeenMutation from '~/graphql/mutation/notifier/mark-seen' import markSeenAfterMutation from '~/graphql/mutation/notifier/mark-seen-after' import markSeenThreadMutation from '~/graphql/mutation/notifier/mark-seen-thread' import getNotifications from '~/graphql/query/notifier/notifications-load' import { NotificationGroup, QueryLoad_NotificationsArgs } from '~/graphql/schema/core.gen' import { NotificationsPanel } from '../components/NotificationsPanel' import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated' import { SSEMessage, useConnect } from './connect' import { useSession } from './session' type NotificationsContextType = { notificationEntities: Record unreadNotificationsCount: Accessor after: Accessor sortedNotifications: Accessor loadedNotificationsCount: Accessor totalNotificationsCount: Accessor showNotificationsPanel: () => void hideNotificationsPanel: () => void markSeen: (notification_id: number) => Promise markSeenThread: (threadId: string) => Promise markSeenAll: () => Promise loadNotificationsGrouped: (options: QueryLoad_NotificationsArgs) => Promise } export const PAGE_SIZE = 20 const NotificationsContext = createContext({ showNotificationsPanel: () => undefined, hideNotificationsPanel: () => undefined } as NotificationsContextType) export function useNotifications() { return useContext(NotificationsContext) } export const NotificationsProvider = (props: { children: JSX.Element }) => { const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false) const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0) const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0) const [notificationEntities, setNotificationEntities] = createStore>({}) const { session, client } = useSession() const { addHandler } = useConnect() const loadNotificationsGrouped = async (options: QueryLoad_NotificationsArgs) => { if (session()?.access_token) { const resp = await client()?.query(getNotifications, options).toPromise() const result = resp?.data?.get_notifications const groups = result?.notifications || [] const total = result?.total || 0 const unread = result?.unread || 0 const newGroupsEntries = groups.reduce( (acc: { [x: string]: NotificationGroup }, group: NotificationGroup) => { acc[group.thread] = group return acc }, {} ) setTotalNotificationsCount(total) setUnreadNotificationsCount(unread) setNotificationEntities(newGroupsEntries) console.debug('[context.notifications] groups updated', groups) return groups } return [] } const sortedNotifications = createMemo(() => { return Object.values(notificationEntities).sort((a, b) => b.updated_at - a.updated_at) }) const now = Math.floor(Date.now() / 1000) const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length) const [after, setAfter] = makePersisted(createSignal(now), { name: 'notifier_timestamp' }) onMount(() => { addHandler((data: SSEMessage) => { if (data.entity === 'reaction' && session()?.access_token) { console.info('[context.notifications] event', data) loadNotificationsGrouped({ after: after() || now, limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) }) } }) setAfter(now) }) const markSeenThread = async (threadId: string) => { await client()?.mutation(markSeenThreadMutation, { threadId }).toPromise() const thread = notificationEntities[threadId] thread.seen = true setNotificationEntities((nnn) => ({ ...nnn, [threadId]: thread })) setUnreadNotificationsCount((oldCount) => oldCount - 1) } const markSeenAll = async () => { if (session()?.access_token) { const _resp = await client()?.mutation(markSeenAfterMutation, { after: after() }).toPromise() await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() }) } } const markSeen = async (notification_id: number) => { if (session()?.access_token) { await client()?.mutation(markSeenMutation, { notification_id }).toPromise() await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() }) } } const showNotificationsPanel = () => { setIsNotificationsPanelOpen(true) } const hideNotificationsPanel = () => { setIsNotificationsPanelOpen(false) } const actions = { showNotificationsPanel, hideNotificationsPanel, markSeenThread, markSeenAll, markSeen, loadNotificationsGrouped } const value: NotificationsContextType = { after, notificationEntities, sortedNotifications, unreadNotificationsCount, loadedNotificationsCount, totalNotificationsCount, ...actions } const handleNotificationPanelClose = () => { setIsNotificationsPanelOpen(false) } return ( {props.children} ) }