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