forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {
2 createContext,
3 useCallback,
4 useContext,
5 useEffect,
6 useMemo,
7 useState,
8} from 'react'
9import {type AppBskyActorDefs} from '@atproto/api'
10
11import {logger} from '#/logger'
12import {STALE} from '#/state/queries'
13import {Nux, useNuxs, useResetNuxs, useSaveNux} from '#/state/queries/nuxs'
14import {
15 usePreferencesQuery,
16 type UsePreferencesQueryResponse,
17} from '#/state/queries/preferences'
18import {useProfileQuery} from '#/state/queries/profile'
19import {type SessionAccount, useSession} from '#/state/session'
20import {useOnboardingState} from '#/state/shell'
21import {
22 DraftsAnnouncement,
23 enabled as isDraftsAnnouncementEnabled,
24} from '#/components/dialogs/nuxs/DraftsAnnouncement'
25import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
26import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils'
27import {useAnalytics} from '#/analytics'
28import {useGeolocation} from '#/geolocation'
29
30type Context = {
31 activeNux: Nux | undefined
32 dismissActiveNux: () => void
33}
34
35const queuedNuxs: {
36 id: Nux
37 enabled?: (props: EnabledCheckProps) => boolean
38}[] = [
39 {
40 id: Nux.DraftsAnnouncement,
41 enabled: isDraftsAnnouncementEnabled,
42 },
43]
44
45const Context = createContext<Context>({
46 activeNux: undefined,
47 dismissActiveNux: () => {},
48})
49Context.displayName = 'NuxDialogContext'
50
51export function useNuxDialogContext() {
52 return useContext(Context)
53}
54
55export function NuxDialogs() {
56 const {currentAccount} = useSession()
57 const {data: preferences} = usePreferencesQuery()
58 const {data: profile} = useProfileQuery({
59 did: currentAccount?.did,
60 staleTime: STALE.INFINITY, // createdAt isn't gonna change
61 })
62 const onboardingActive = useOnboardingState().isActive
63
64 const isLoading =
65 onboardingActive ||
66 !currentAccount ||
67 !preferences ||
68 !profile ||
69 // Profile isn't legit ready until createdAt is a real date.
70 !profile.createdAt ||
71 profile.createdAt === '0001-01-01T00:00:00.000Z' // TODO: Fix this in AppView.
72
73 return !isLoading ? (
74 <Inner
75 currentAccount={currentAccount}
76 currentProfile={profile}
77 preferences={preferences}
78 />
79 ) : null
80}
81
82function Inner({
83 currentAccount,
84 currentProfile,
85 preferences,
86}: {
87 currentAccount: SessionAccount
88 currentProfile: AppBskyActorDefs.ProfileViewDetailed
89 preferences: UsePreferencesQueryResponse
90}) {
91 const ax = useAnalytics()
92 const geolocation = useGeolocation()
93 const {nuxs} = useNuxs()
94 const [snoozed, setSnoozed] = useState(() => {
95 return isSnoozed()
96 })
97 const [activeNux, setActiveNux] = useState<Nux | undefined>()
98 const {mutateAsync: saveNux} = useSaveNux()
99 const {mutate: resetNuxs} = useResetNuxs()
100
101 const snoozeNuxDialog = useCallback(() => {
102 snooze()
103 setSnoozed(true)
104 }, [setSnoozed])
105
106 const dismissActiveNux = useCallback(() => {
107 if (!activeNux) return
108 setActiveNux(undefined)
109 }, [activeNux, setActiveNux])
110
111 if (__DEV__ && typeof window !== 'undefined') {
112 // @ts-ignore
113 window.clearNuxDialog = (id: Nux) => {
114 if (!__DEV__ || !id) return
115 resetNuxs([id])
116 unsnooze()
117 }
118 }
119
120 useEffect(() => {
121 if (snoozed) return // comment this out to test
122 if (!nuxs) return
123
124 for (const {id, enabled} of queuedNuxs) {
125 const nux = nuxs.find(nux => nux.id === id)
126
127 // check if completed first
128 if (nux && nux.completed) {
129 continue // comment this out to test
130 }
131
132 // then check gate (track exposure)
133 if (
134 enabled &&
135 !enabled({
136 features: ax.features,
137 currentAccount,
138 currentProfile,
139 preferences,
140 geolocation,
141 })
142 ) {
143 continue
144 }
145
146 logger.debug(`NUX dialogs: activating '${id}' NUX`)
147
148 // we have a winner
149 setActiveNux(id)
150
151 // immediately snooze for a day
152 snoozeNuxDialog()
153
154 // immediately update remote data (affects next reload)
155 saveNux({
156 id,
157 completed: true,
158 data: undefined,
159 }).catch(e => {
160 logger.error(`NUX dialogs: failed to upsert '${id}' NUX`, {
161 safeMessage: e.message,
162 })
163 })
164
165 break
166 }
167 }, [
168 ax.features,
169 nuxs,
170 snoozed,
171 snoozeNuxDialog,
172 saveNux,
173 currentAccount,
174 currentProfile,
175 preferences,
176 geolocation,
177 ])
178
179 const ctx = useMemo(() => {
180 return {
181 activeNux,
182 dismissActiveNux,
183 }
184 }, [activeNux, dismissActiveNux])
185
186 return (
187 <Context.Provider value={ctx}>
188 {/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
189 {activeNux === Nux.DraftsAnnouncement && <DraftsAnnouncement />}
190 </Context.Provider>
191 )
192}