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 {logger} from '#/logger'
6import {
7 preferencesQueryKey,
8 usePreferencesQuery,
9} from '#/state/queries/preferences'
10import {useAgent} from '#/state/session'
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 queryClient = useQueryClient()
67 const agent = useAgent()
68
69 return useMutation<
70 AppBskyActorDefs.LiveEventPreferences,
71 Error,
72 LiveEventPreferencesAction,
73 {undoAction: LiveEventPreferencesAction | null}
74 >({
75 onSettled(data, error, variables) {
76 /*
77 * `onSettled` runs after the mutation completes, success or no. The idea
78 * here is that we want to invert the action that was just passed in, and
79 * provide it as an `undoAction` to the `onUpdateSuccess` callback.
80 *
81 * If the operation was not a success, we don't provide the `undoAction`.
82 *
83 * Upon the first call of the mutation, the `__canUndo` flag is undefined,
84 * so we allow the undo. However, when we create the `undoAction`, we
85 * set its `__canUndo` flag to false, so that if the user were to call
86 * the undo action, we would not provide another undo for that.
87 */
88 const canUndo = variables.__canUndo === undefined ? true : false
89 let undoAction: LiveEventPreferencesAction | null = null
90
91 switch (variables.type) {
92 case 'hideFeed':
93 undoAction = {type: 'unhideFeed', id: variables.id, __canUndo: false}
94 break
95 case 'unhideFeed':
96 undoAction = {type: 'hideFeed', id: variables.id, __canUndo: false}
97 break
98 case 'toggleHideAllFeeds':
99 undoAction = {type: 'toggleHideAllFeeds', __canUndo: false}
100 break
101 }
102
103 if (data && !error) {
104 props?.onUpdateSuccess?.({
105 undoAction: canUndo ? undoAction : null,
106 })
107 }
108 },
109 mutationFn: async action => {
110 const updated = await agent.updateLiveEventPreferences(action)
111 const prefs = updated.find(p =>
112 asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p),
113 )
114
115 switch (action.type) {
116 case 'hideFeed':
117 case 'unhideFeed': {
118 if (!props.feed) {
119 logger.error(
120 `useUpdateLiveEventPreferences: feed is missing, but required for hiding/unhiding`,
121 {
122 action,
123 },
124 )
125 break
126 }
127
128 logger.metric(
129 action.type === 'hideFeed'
130 ? 'liveEvents:feedBanner:hide'
131 : 'liveEvents:feedBanner:unhide',
132 {
133 feed: props.feed.url,
134 context: props.metricContext,
135 },
136 )
137 break
138 }
139 case 'toggleHideAllFeeds': {
140 if (prefs!.hideAllFeeds) {
141 logger.metric('liveEvents:hideAllFeedBanners', {
142 context: props.metricContext,
143 })
144 } else {
145 logger.metric('liveEvents:unhideAllFeedBanners', {
146 context: props.metricContext,
147 })
148 }
149 break
150 }
151 }
152
153 // triggers a refetch
154 queryClient.invalidateQueries({
155 queryKey: preferencesQueryKey,
156 })
157
158 return prefs!
159 },
160 })
161}