Bluesky app fork with some witchin' additions 馃挮
at main 192 lines 4.7 kB view raw
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}