React-query-ify notifications (#812)

* Use single react query to subscribe to notifications

* Remove 'preferred' in variable names
This commit is contained in:
James Grugett 2022-08-28 15:20:21 -05:00 committed by GitHub
parent 3d073da97e
commit 9c15d5b96c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 116 deletions

View File

@ -19,7 +19,7 @@ import { sum } from 'lodash'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { useWindowSize } from 'web/hooks/use-window-size' import { useWindowSize } from 'web/hooks/use-window-size'
import { Content, useTextEditor } from 'web/components/editor' import { Content, useTextEditor } from 'web/components/editor'
import { useUnseenPreferredNotifications } from 'web/hooks/use-notifications' import { useUnseenNotifications } from 'web/hooks/use-notifications'
import { ChevronDownIcon, UsersIcon } from '@heroicons/react/outline' import { ChevronDownIcon, UsersIcon } from '@heroicons/react/outline'
import { setNotificationsAsSeen } from 'web/pages/notifications' import { setNotificationsAsSeen } from 'web/pages/notifications'
import { usePrivateUser } from 'web/hooks/use-user' import { usePrivateUser } from 'web/hooks/use-user'
@ -277,14 +277,18 @@ function GroupChatNotificationsIcon(props: {
hidden: boolean hidden: boolean
}) { }) {
const { privateUser, group, shouldSetAsSeen, hidden } = props const { privateUser, group, shouldSetAsSeen, hidden } = props
const preferredNotificationsForThisGroup = useUnseenPreferredNotifications( const notificationsForThisGroup = useUnseenNotifications(
privateUser, privateUser
{ // Disabled tracking by customHref for now.
customHref: `/group/${group.slug}`, // {
} // customHref: `/group/${group.slug}`,
// }
) )
useEffect(() => { useEffect(() => {
preferredNotificationsForThisGroup.forEach((notification) => { if (!notificationsForThisGroup) return
notificationsForThisGroup.forEach((notification) => {
if ( if (
(shouldSetAsSeen && notification.isSeenOnHref?.includes('chat')) || (shouldSetAsSeen && notification.isSeenOnHref?.includes('chat')) ||
// old style chat notif that simply ended with the group slug // old style chat notif that simply ended with the group slug
@ -293,13 +297,14 @@ function GroupChatNotificationsIcon(props: {
setNotificationsAsSeen([notification]) setNotificationsAsSeen([notification])
} }
}) })
}, [group.slug, preferredNotificationsForThisGroup, shouldSetAsSeen]) }, [group.slug, notificationsForThisGroup, shouldSetAsSeen])
return ( return (
<div <div
className={ className={
!hidden && !hidden &&
preferredNotificationsForThisGroup.length > 0 && notificationsForThisGroup &&
notificationsForThisGroup.length > 0 &&
!shouldSetAsSeen !shouldSetAsSeen
? 'absolute right-4 top-4 h-3 w-3 rounded-full border-2 border-white bg-red-500' ? 'absolute right-4 top-4 h-3 w-3 rounded-full border-2 border-white bg-red-500'
: 'hidden' : 'hidden'

View File

@ -4,7 +4,7 @@ import { Row } from 'web/components/layout/row'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { usePrivateUser } from 'web/hooks/use-user' import { usePrivateUser } from 'web/hooks/use-user'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useUnseenPreferredNotificationGroups } from 'web/hooks/use-notifications' import { useUnseenGroupedNotification } from 'web/hooks/use-notifications'
import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications' import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications'
import { PrivateUser } from 'common/user' import { PrivateUser } from 'common/user'
@ -30,7 +30,7 @@ function UnseenNotificationsBubble(props: { privateUser: PrivateUser }) {
else setSeen(false) else setSeen(false)
}, [router.pathname]) }, [router.pathname])
const notifications = useUnseenPreferredNotificationGroups(privateUser) const notifications = useUnseenGroupedNotification(privateUser)
if (!notifications || notifications.length === 0 || seen) { if (!notifications || notifications.length === 0 || seen) {
return <div /> return <div />
} }

View File

@ -1,13 +1,9 @@
import { useEffect, useMemo, useState } from 'react' import { useMemo } from 'react'
import { notification_subscribe_types, PrivateUser } from 'common/user' import { notification_subscribe_types, PrivateUser } from 'common/user'
import { Notification } from 'common/notification' import { Notification } from 'common/notification'
import { import { getNotificationsQuery } from 'web/lib/firebase/notifications'
getNotificationsQuery,
listenForNotifications,
} from 'web/lib/firebase/notifications'
import { groupBy, map, partition } from 'lodash' import { groupBy, map, partition } from 'lodash'
import { useFirestoreQueryData } from '@react-query-firebase/firestore' import { useFirestoreQueryData } from '@react-query-firebase/firestore'
import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications'
export type NotificationGroup = { export type NotificationGroup = {
notifications: Notification[] notifications: Notification[]
@ -17,49 +13,48 @@ export type NotificationGroup = {
type: 'income' | 'normal' type: 'income' | 'normal'
} }
// For some reason react-query subscriptions don't actually listen for notifications function useNotifications(privateUser: PrivateUser) {
// Use useUnseenPreferredNotificationGroups to listen for new notifications
export function usePreferredGroupedNotifications(
privateUser: PrivateUser,
cachedNotifications?: Notification[]
) {
const result = useFirestoreQueryData( const result = useFirestoreQueryData(
['notifications-all', privateUser.id], ['notifications-all', privateUser.id],
getNotificationsQuery(privateUser.id) getNotificationsQuery(privateUser.id),
{ subscribe: true, includeMetadataChanges: true },
// Temporary workaround for react-query bug:
// https://github.com/invertase/react-query-firebase/issues/25
{ cacheTime: 0 }
) )
const notifications = useMemo(() => { const notifications = useMemo(() => {
if (result.isLoading) return cachedNotifications ?? [] if (!result.data) return undefined
if (!result.data) return cachedNotifications ?? []
const notifications = result.data as Notification[] const notifications = result.data as Notification[]
return getAppropriateNotifications( return getAppropriateNotifications(
notifications, notifications,
privateUser.notificationPreferences privateUser.notificationPreferences
).filter((n) => !n.isSeenOnHref) ).filter((n) => !n.isSeenOnHref)
}, [ }, [privateUser.notificationPreferences, result.data])
cachedNotifications,
privateUser.notificationPreferences,
result.data,
result.isLoading,
])
return notifications
}
export function useUnseenNotifications(privateUser: PrivateUser) {
const notifications = useNotifications(privateUser)
return useMemo(
() => notifications && notifications.filter((n) => !n.isSeen),
[notifications]
)
}
export function useGroupedNotifications(privateUser: PrivateUser) {
const notifications = useNotifications(privateUser)
return useMemo(() => { return useMemo(() => {
if (notifications) return groupNotifications(notifications) if (notifications) return groupNotifications(notifications)
}, [notifications]) }, [notifications])
} }
export function useUnseenPreferredNotificationGroups(privateUser: PrivateUser) { export function useUnseenGroupedNotification(privateUser: PrivateUser) {
const notifications = useUnseenPreferredNotifications(privateUser, {}) const notifications = useUnseenNotifications(privateUser)
const [notificationGroups, setNotificationGroups] = useState< return useMemo(() => {
NotificationGroup[] | undefined if (notifications) return groupNotifications(notifications)
>(undefined)
useEffect(() => {
if (!notifications) return
const groupedNotifications = groupNotifications(notifications)
setNotificationGroups(groupedNotifications)
}, [notifications]) }, [notifications])
return notificationGroups
} }
export function groupNotifications(notifications: Notification[]) { export function groupNotifications(notifications: Notification[]) {
@ -114,36 +109,6 @@ export function groupNotifications(notifications: Notification[]) {
return notificationGroups return notificationGroups
} }
export function useUnseenPreferredNotifications(
privateUser: PrivateUser,
options: { customHref?: string },
limit: number = NOTIFICATIONS_PER_PAGE
) {
const { customHref } = options
const [notifications, setNotifications] = useState<Notification[]>([])
const [userAppropriateNotifications, setUserAppropriateNotifications] =
useState<Notification[]>([])
useEffect(() => {
return listenForNotifications(privateUser.id, setNotifications, {
unseenOnly: true,
limit,
})
}, [limit, privateUser.id])
useEffect(() => {
const notificationsToShow = getAppropriateNotifications(
notifications,
privateUser.notificationPreferences
).filter((n) =>
customHref ? n.isSeenOnHref?.includes(customHref) : !n.isSeenOnHref
)
setUserAppropriateNotifications(notificationsToShow)
}, [notifications, customHref, privateUser.notificationPreferences])
return userAppropriateNotifications
}
const lessPriorityReasons = [ const lessPriorityReasons = [
'on_contract_with_users_comment', 'on_contract_with_users_comment',
'on_contract_with_users_answer', 'on_contract_with_users_answer',

View File

@ -1,7 +1,5 @@
import { collection, limit, orderBy, query, where } from 'firebase/firestore' import { collection, limit, orderBy, query, where } from 'firebase/firestore'
import { Notification } from 'common/notification'
import { db } from 'web/lib/firebase/init' import { db } from 'web/lib/firebase/init'
import { listenForValues } from 'web/lib/firebase/utils'
import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications' import { NOTIFICATIONS_PER_PAGE } from 'web/pages/notifications'
export function getNotificationsQuery( export function getNotificationsQuery(
@ -23,17 +21,3 @@ export function getNotificationsQuery(
limit(NOTIFICATIONS_PER_PAGE * 10) limit(NOTIFICATIONS_PER_PAGE * 10)
) )
} }
export function listenForNotifications(
userId: string,
setNotifications: (notifs: Notification[]) => void,
unseenOnlyOptions?: { unseenOnly: boolean; limit: number }
) {
return listenForValues<Notification>(
getNotificationsQuery(userId, unseenOnlyOptions),
(notifs) => {
notifs.sort((n1, n2) => n2.createdTime - n1.createdTime)
setNotifications(notifs)
}
)
}

View File

@ -26,7 +26,7 @@ import {
} from 'web/components/outcome-label' } from 'web/components/outcome-label'
import { import {
NotificationGroup, NotificationGroup,
usePreferredGroupedNotifications, useGroupedNotifications,
} from 'web/hooks/use-notifications' } from 'web/hooks/use-notifications'
import { TrendingUpIcon } from '@heroicons/react/outline' import { TrendingUpIcon } from '@heroicons/react/outline'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
@ -45,6 +45,7 @@ import { SiteLink } from 'web/components/site-link'
import { NotificationSettings } from 'web/components/NotificationSettings' import { NotificationSettings } from 'web/components/NotificationSettings'
import { SEO } from 'web/components/SEO' import { SEO } from 'web/components/SEO'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { LoadingIndicator } from 'web/components/loading-indicator'
export const NOTIFICATIONS_PER_PAGE = 30 export const NOTIFICATIONS_PER_PAGE = 30
const MULTIPLE_USERS_KEY = 'multipleUsers' const MULTIPLE_USERS_KEY = 'multipleUsers'
@ -58,16 +59,6 @@ export default function Notifications(props: {
auth: { privateUser: PrivateUser } auth: { privateUser: PrivateUser }
}) { }) {
const { privateUser } = props.auth const { privateUser } = props.auth
const local = safeLocalStorage()
let localNotifications = [] as Notification[]
const localSavedNotificationGroups = local?.getItem('notification-groups')
let localNotificationGroups = [] as NotificationGroup[]
if (localSavedNotificationGroups) {
localNotificationGroups = JSON.parse(localSavedNotificationGroups)
localNotifications = localNotificationGroups
.map((g) => g.notifications)
.flat()
}
return ( return (
<Page> <Page>
@ -84,12 +75,7 @@ export default function Notifications(props: {
tabs={[ tabs={[
{ {
title: 'Notifications', title: 'Notifications',
content: ( content: <NotificationsList privateUser={privateUser} />,
<NotificationsList
privateUser={privateUser}
cachedNotifications={localNotifications}
/>
),
}, },
{ {
title: 'Settings', title: 'Settings',
@ -135,16 +121,10 @@ function RenderNotificationGroups(props: {
) )
} }
function NotificationsList(props: { function NotificationsList(props: { privateUser: PrivateUser }) {
privateUser: PrivateUser const { privateUser } = props
cachedNotifications: Notification[]
}) {
const { privateUser, cachedNotifications } = props
const [page, setPage] = useState(0) const [page, setPage] = useState(0)
const allGroupedNotifications = usePreferredGroupedNotifications( const allGroupedNotifications = useGroupedNotifications(privateUser)
privateUser,
cachedNotifications
)
const paginatedGroupedNotifications = useMemo(() => { const paginatedGroupedNotifications = useMemo(() => {
if (!allGroupedNotifications) return if (!allGroupedNotifications) return
const start = page * NOTIFICATIONS_PER_PAGE const start = page * NOTIFICATIONS_PER_PAGE
@ -163,7 +143,8 @@ function NotificationsList(props: {
return maxNotificationsToShow return maxNotificationsToShow
}, [allGroupedNotifications, page]) }, [allGroupedNotifications, page])
if (!paginatedGroupedNotifications || !allGroupedNotifications) return <div /> if (!paginatedGroupedNotifications || !allGroupedNotifications)
return <LoadingIndicator />
return ( return (
<div className={'min-h-[100vh] text-sm'}> <div className={'min-h-[100vh] text-sm'}>