React-query-ify notifications (#812)
* Use single react query to subscribe to notifications * Remove 'preferred' in variable names
This commit is contained in:
parent
3d073da97e
commit
9c15d5b96c
|
@ -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'
|
||||||
|
|
|
@ -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 />
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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'}>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user