forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useEffect} from 'react'
2import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api'
3import {useMutation, useQueryClient} from '@tanstack/react-query'
4
5import {
6 preferencesQueryKey,
7 usePreferencesQuery,
8} from '#/state/queries/preferences'
9import {useAgent} from '#/state/session'
10import {pdsAgent} from '#/state/session/agent'
11import {useAnalytics} from '#/analytics'
12import {IS_WEB} from '#/env'
13import * as env from '#/env'
14import {
15 type LiveEventFeed,
16 type LiveEventFeedMetricContext,
17} from '#/features/liveEvents/types'
18
19export type LiveEventPreferencesAction = Parameters<
20 Agent['updateLiveEventPreferences']
21>[0] & {
22 /**
23 * Flag that is internal to this hook, do not set when updating prefs
24 */
25 __canUndo?: boolean
26}
27
28export function useLiveEventPreferences() {
29 const query = usePreferencesQuery()
30 useWebOnlyDebugLiveEventPreferences()
31 return {
32 ...query,
33 data: query.data?.liveEventPreferences || {
34 hideAllFeeds: false,
35 hiddenFeedIds: [],
36 },
37 }
38}
39
40function useWebOnlyDebugLiveEventPreferences() {
41 const queryClient = useQueryClient()
42 const agent = useAgent()
43
44 useEffect(() => {
45 if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') {
46 // @ts-ignore
47 window.__updateLiveEventPreferences = async (
48 action: LiveEventPreferencesAction,
49 ) => {
50 await pdsAgent(agent).updateLiveEventPreferences(action)
51 // triggers a refetch
52 await queryClient.invalidateQueries({
53 queryKey: preferencesQueryKey,
54 })
55 }
56 }
57 }, [agent, queryClient])
58}
59
60export function useUpdateLiveEventPreferences(props: {
61 feed?: LiveEventFeed
62 metricContext: LiveEventFeedMetricContext
63 onUpdateSuccess?: (props: {
64 undoAction: LiveEventPreferencesAction | null
65 }) => void
66}) {
67 const ax = useAnalytics()
68 const queryClient = useQueryClient()
69 const agent = useAgent()
70
71 return useMutation<
72 AppBskyActorDefs.LiveEventPreferences,
73 Error,
74 LiveEventPreferencesAction,
75 {undoAction: LiveEventPreferencesAction | null}
76 >({
77 onSettled(data, error, variables) {
78 /*
79 * `onSettled` runs after the mutation completes, success or no. The idea
80 * here is that we want to invert the action that was just passed in, and
81 * provide it as an `undoAction` to the `onUpdateSuccess` callback.
82 *
83 * If the operation was not a success, we don't provide the `undoAction`.
84 *
85 * Upon the first call of the mutation, the `__canUndo` flag is undefined,
86 * so we allow the undo. However, when we create the `undoAction`, we
87 * set its `__canUndo` flag to false, so that if the user were to call
88 * the undo action, we would not provide another undo for that.
89 */
90 const canUndo = variables.__canUndo === undefined ? true : false
91 let undoAction: LiveEventPreferencesAction | null = null
92
93 switch (variables.type) {
94 case 'hideFeed':
95 undoAction = {type: 'unhideFeed', id: variables.id, __canUndo: false}
96 break
97 case 'unhideFeed':
98 undoAction = {type: 'hideFeed', id: variables.id, __canUndo: false}
99 break
100 case 'toggleHideAllFeeds':
101 undoAction = {type: 'toggleHideAllFeeds', __canUndo: false}
102 break
103 }
104
105 if (data && !error) {
106 props?.onUpdateSuccess?.({
107 undoAction: canUndo ? undoAction : null,
108 })
109 }
110 },
111 mutationFn: async action => {
112 const updated = await pdsAgent(agent).updateLiveEventPreferences(action)
113 const prefs = updated.find(p =>
114 asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p),
115 )
116
117 switch (action.type) {
118 case 'hideFeed':
119 case 'unhideFeed': {
120 if (!props.feed) {
121 ax.logger.error(
122 `useUpdateLiveEventPreferences: feed is missing, but required for hiding/unhiding`,
123 {
124 action,
125 },
126 )
127 break
128 }
129
130 ax.metric(
131 action.type === 'hideFeed'
132 ? 'liveEvents:feedBanner:hide'
133 : 'liveEvents:feedBanner:unhide',
134 {
135 feed: props.feed.url,
136 context: props.metricContext,
137 },
138 )
139 break
140 }
141 case 'toggleHideAllFeeds': {
142 if (prefs!.hideAllFeeds) {
143 ax.metric('liveEvents:hideAllFeedBanners', {
144 context: props.metricContext,
145 })
146 } else {
147 ax.metric('liveEvents:unhideAllFeedBanners', {
148 context: props.metricContext,
149 })
150 }
151 break
152 }
153 }
154
155 // triggers a refetch
156 queryClient.invalidateQueries({
157 queryKey: preferencesQueryKey,
158 })
159
160 return prefs!
161 },
162 })
163}