Priority notifications (#4798)
* new settings screen * bring back the spinner * add experimental language * fix typo, change leading * integrate priority notifications API * update package * use refetch instead of invalidateQueries * fix read-after-write issue by polling for update * add spinner for initial load * rm onmutate, it's overcomplicated * set error state eagerly * Change language in description Co-authored-by: Hailey <me@haileyok.com> * prettier * add `Toggle.Platform` * extract out mutation hook + error state * rm useless cache mutation * disambiguate isError and isPending * rm unused isError --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
parent
9bd8393685
commit
cfb8a3160e
20 changed files with 305 additions and 84 deletions
|
@ -46,11 +46,14 @@ const PAGE_SIZE = 30
|
|||
type RQPageParam = string | undefined
|
||||
|
||||
const RQKEY_ROOT = 'notification-feed'
|
||||
export function RQKEY() {
|
||||
return [RQKEY_ROOT]
|
||||
export function RQKEY(priority?: false) {
|
||||
return [RQKEY_ROOT, priority]
|
||||
}
|
||||
|
||||
export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
||||
export function useNotificationFeedQuery(opts?: {
|
||||
enabled?: boolean
|
||||
overridePriorityNotifications?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
@ -59,6 +62,10 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
|||
const lastPageCountRef = useRef(0)
|
||||
const gate = useGate()
|
||||
|
||||
// false: force showing all notifications
|
||||
// undefined: let the server decide
|
||||
const priority = opts?.overridePriorityNotifications ? false : undefined
|
||||
|
||||
const query = useInfiniteQuery<
|
||||
FeedPage,
|
||||
Error,
|
||||
|
@ -67,7 +74,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
|||
RQPageParam
|
||||
>({
|
||||
staleTime: STALE.INFINITY,
|
||||
queryKey: RQKEY(),
|
||||
queryKey: RQKEY(priority),
|
||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||
let page
|
||||
if (!pageParam) {
|
||||
|
@ -75,17 +82,17 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
|||
page = unreads.getCachedUnreadPage()
|
||||
}
|
||||
if (!page) {
|
||||
page = (
|
||||
await fetchPage({
|
||||
agent,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
queryClient,
|
||||
moderationOpts,
|
||||
fetchAdditionalData: true,
|
||||
shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'),
|
||||
})
|
||||
).page
|
||||
const {page: fetchedPage} = await fetchPage({
|
||||
agent,
|
||||
limit: PAGE_SIZE,
|
||||
cursor: pageParam,
|
||||
queryClient,
|
||||
moderationOpts,
|
||||
fetchAdditionalData: true,
|
||||
shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'),
|
||||
priority,
|
||||
})
|
||||
page = fetchedPage
|
||||
}
|
||||
|
||||
// if the first page has an unread, mark all read
|
||||
|
|
67
src/state/queries/notifications/settings.ts
Normal file
67
src/state/queries/notifications/settings.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {until} from '#/lib/async/until'
|
||||
import {logger} from '#/logger'
|
||||
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
|
||||
export function useNotificationsSettingsMutation() {
|
||||
const {_} = useLingui()
|
||||
const agent = useAgent()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (keys: string[]) => {
|
||||
const enabled = keys[0] === 'enabled'
|
||||
|
||||
await agent.api.app.bsky.notification.putPreferences({
|
||||
priority: enabled,
|
||||
})
|
||||
|
||||
await until(
|
||||
5, // 5 tries
|
||||
1e3, // 1s delay between tries
|
||||
res => res.data.priority === enabled,
|
||||
() => agent.api.app.bsky.notification.listNotifications({limit: 1}),
|
||||
)
|
||||
|
||||
eagerlySetCachedPriority(queryClient, enabled)
|
||||
},
|
||||
onError: err => {
|
||||
logger.error('Failed to save notification preferences', {
|
||||
safeMessage: err,
|
||||
})
|
||||
Toast.show(
|
||||
_(msg`Failed to save notification preferences, please try again`),
|
||||
'xmark',
|
||||
)
|
||||
},
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Preference saved`))
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS()})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function eagerlySetCachedPriority(
|
||||
queryClient: ReturnType<typeof useQueryClient>,
|
||||
enabled: boolean,
|
||||
) {
|
||||
queryClient.setQueryData(RQKEY_NOTIFS(), (old: any) => {
|
||||
if (!old) return old
|
||||
return {
|
||||
...old,
|
||||
pages: old.pages.map((page: any) => {
|
||||
return {
|
||||
...page,
|
||||
priority: enabled,
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -22,6 +22,7 @@ export interface FeedPage {
|
|||
cursor: string | undefined
|
||||
seenAt: Date
|
||||
items: FeedNotification[]
|
||||
priority: boolean
|
||||
}
|
||||
|
||||
export interface CachedFeedPage {
|
||||
|
|
|
@ -39,10 +39,15 @@ export async function fetchPage({
|
|||
moderationOpts: ModerationOpts | undefined
|
||||
fetchAdditionalData: boolean
|
||||
shouldUngroupFollowBacks?: () => boolean
|
||||
}): Promise<{page: FeedPage; indexedAt: string | undefined}> {
|
||||
priority?: boolean
|
||||
}): Promise<{
|
||||
page: FeedPage
|
||||
indexedAt: string | undefined
|
||||
}> {
|
||||
const res = await agent.listNotifications({
|
||||
limit,
|
||||
cursor,
|
||||
// priority,
|
||||
})
|
||||
|
||||
const indexedAt = res.data.notifications[0]?.indexedAt
|
||||
|
@ -88,6 +93,7 @@ export async function fetchPage({
|
|||
cursor: res.data.cursor,
|
||||
seenAt,
|
||||
items: notifsGrouped,
|
||||
priority: res.data.priority ?? false,
|
||||
},
|
||||
indexedAt,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue