[APP-1767] Live event feeds (#9696)
* Update env config for new worker setup * Add liveEvents context * Add live event feed card to Explore * Add Discover banner/trending handling, device storage * Add prefs methods, hook up to Discover banner * Add settings toggle * Fix up metrics and mutations * Simplify undo logic * Handle previews * Update overlay color and text color handling * Update metrics * Handle live event in feed card * Show multiple feeds on Explore for bsky team * Fix dev/preview handling bug * Add sidebar cards, update types * Fix type error * Fix type error * Feedback * Blurhash fix * Copy update * Dev only debug hook
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import {useEffect} from 'react'
|
||||
import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {
|
||||
preferencesQueryKey,
|
||||
usePreferencesQuery,
|
||||
} from '#/state/queries/preferences'
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as env from '#/env'
|
||||
import {
|
||||
type LiveEventFeed,
|
||||
type LiveEventFeedMetricContext,
|
||||
} from '#/features/liveEvents/types'
|
||||
|
||||
export type LiveEventPreferencesAction = Parameters<
|
||||
Agent['updateLiveEventPreferences']
|
||||
>[0] & {
|
||||
/**
|
||||
* Flag that is internal to this hook, do not set when updating prefs
|
||||
*/
|
||||
__canUndo?: boolean
|
||||
}
|
||||
|
||||
export function useLiveEventPreferences() {
|
||||
const query = usePreferencesQuery()
|
||||
useWebOnlyDebugLiveEventPreferences()
|
||||
return {
|
||||
...query,
|
||||
data: query.data?.liveEventPreferences || {
|
||||
hideAllFeeds: false,
|
||||
hiddenFeedIds: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function useWebOnlyDebugLiveEventPreferences() {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
|
||||
useEffect(() => {
|
||||
if (env.IS_DEV && isWeb && typeof window !== 'undefined') {
|
||||
// @ts-ignore
|
||||
window.__updateLiveEventPreferences = async (
|
||||
action: LiveEventPreferencesAction,
|
||||
) => {
|
||||
await agent.updateLiveEventPreferences(action)
|
||||
// triggers a refetch
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [agent, queryClient])
|
||||
}
|
||||
|
||||
export function useUpdateLiveEventPreferences(props: {
|
||||
feed?: LiveEventFeed
|
||||
metricContext: LiveEventFeedMetricContext
|
||||
onUpdateSuccess?: (props: {
|
||||
undoAction: LiveEventPreferencesAction | null
|
||||
}) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
|
||||
return useMutation<
|
||||
AppBskyActorDefs.LiveEventPreferences,
|
||||
Error,
|
||||
LiveEventPreferencesAction,
|
||||
{undoAction: LiveEventPreferencesAction | null}
|
||||
>({
|
||||
onSettled(data, error, variables) {
|
||||
/*
|
||||
* `onSettled` runs after the mutation completes, success or no. The idea
|
||||
* here is that we want to invert the action that was just passed in, and
|
||||
* provide it as an `undoAction` to the `onUpdateSuccess` callback.
|
||||
*
|
||||
* If the operation was not a success, we don't provide the `undoAction`.
|
||||
*
|
||||
* Upon the first call of the mutation, the `__canUndo` flag is undefined,
|
||||
* so we allow the undo. However, when we create the `undoAction`, we
|
||||
* set its `__canUndo` flag to false, so that if the user were to call
|
||||
* the undo action, we would not provide another undo for that.
|
||||
*/
|
||||
const canUndo = variables.__canUndo === undefined ? true : false
|
||||
let undoAction: LiveEventPreferencesAction | null = null
|
||||
|
||||
switch (variables.type) {
|
||||
case 'hideFeed':
|
||||
undoAction = {type: 'unhideFeed', id: variables.id, __canUndo: false}
|
||||
break
|
||||
case 'unhideFeed':
|
||||
undoAction = {type: 'hideFeed', id: variables.id, __canUndo: false}
|
||||
break
|
||||
case 'toggleHideAllFeeds':
|
||||
undoAction = {type: 'toggleHideAllFeeds', __canUndo: false}
|
||||
break
|
||||
}
|
||||
|
||||
if (data && !error) {
|
||||
props?.onUpdateSuccess?.({
|
||||
undoAction: canUndo ? undoAction : null,
|
||||
})
|
||||
}
|
||||
},
|
||||
mutationFn: async action => {
|
||||
const updated = await agent.updateLiveEventPreferences(action)
|
||||
const prefs = updated.find(p =>
|
||||
asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p),
|
||||
)
|
||||
|
||||
switch (action.type) {
|
||||
case 'hideFeed':
|
||||
case 'unhideFeed': {
|
||||
if (!props.feed) {
|
||||
logger.error(
|
||||
`useUpdateLiveEventPreferences: feed is missing, but required for hiding/unhiding`,
|
||||
{
|
||||
action,
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
logger.metric(
|
||||
action.type === 'hideFeed'
|
||||
? 'liveEvents:feedBanner:hide'
|
||||
: 'liveEvents:feedBanner:unhide',
|
||||
{
|
||||
feed: props.feed.url,
|
||||
context: props.metricContext,
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'toggleHideAllFeeds': {
|
||||
if (prefs!.hideAllFeeds) {
|
||||
logger.metric('liveEvents:hideAllFeedBanners', {
|
||||
context: props.metricContext,
|
||||
})
|
||||
} else {
|
||||
logger.metric('liveEvents:unhideAllFeedBanners', {
|
||||
context: props.metricContext,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// triggers a refetch
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: preferencesQueryKey,
|
||||
})
|
||||
|
||||
return prefs!
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user