mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at tooltip 7.8 kB view raw
1import React, {useMemo} from 'react' 2import {msg} from '@lingui/macro' 3import {useLingui} from '@lingui/react' 4 5import {logEvent} from '#/lib/statsig/statsig' 6import { 7 ProgressGuideToast, 8 ProgressGuideToastRef, 9} from '#/components/ProgressGuide/Toast' 10import { 11 usePreferencesQuery, 12 useSetActiveProgressGuideMutation, 13} from '../queries/preferences' 14 15export enum ProgressGuideAction { 16 Like = 'like', 17 Follow = 'follow', 18} 19 20type ProgressGuideName = 'like-10-and-follow-7' | 'follow-10' 21 22/** 23 * Progress Guides that extend this interface must specify their name in the `guide` field, so it can be used as a discriminated union 24 */ 25interface BaseProgressGuide { 26 guide: ProgressGuideName 27 isComplete: boolean 28 [key: string]: any 29} 30 31export interface Like10AndFollow7ProgressGuide extends BaseProgressGuide { 32 guide: 'like-10-and-follow-7' 33 numLikes: number 34 numFollows: number 35} 36 37export interface Follow10ProgressGuide extends BaseProgressGuide { 38 guide: 'follow-10' 39 numFollows: number 40} 41 42export type ProgressGuide = 43 | Like10AndFollow7ProgressGuide 44 | Follow10ProgressGuide 45 | undefined 46 47const ProgressGuideContext = React.createContext<ProgressGuide>(undefined) 48 49const ProgressGuideControlContext = React.createContext<{ 50 startProgressGuide(guide: ProgressGuideName): void 51 endProgressGuide(): void 52 captureAction(action: ProgressGuideAction, count?: number): void 53}>({ 54 startProgressGuide: (_guide: ProgressGuideName) => {}, 55 endProgressGuide: () => {}, 56 captureAction: (_action: ProgressGuideAction, _count = 1) => {}, 57}) 58 59export function useProgressGuide(guide: ProgressGuideName) { 60 const ctx = React.useContext(ProgressGuideContext) 61 if (ctx?.guide === guide) { 62 return ctx 63 } 64 return undefined 65} 66 67export function useProgressGuideControls() { 68 return React.useContext(ProgressGuideControlContext) 69} 70 71export function Provider({children}: React.PropsWithChildren<{}>) { 72 const {_} = useLingui() 73 const {data: preferences} = usePreferencesQuery() 74 const {mutateAsync, variables, isPending} = 75 useSetActiveProgressGuideMutation() 76 77 const activeProgressGuide = useMemo(() => { 78 const rawProgressGuide = ( 79 isPending ? variables : preferences?.bskyAppState?.activeProgressGuide 80 ) as ProgressGuide 81 82 if (!rawProgressGuide) return undefined 83 84 // ensure the unspecced attributes have the correct types 85 // clone then mutate 86 const {...maybeWronglyTypedProgressGuide} = rawProgressGuide 87 if (maybeWronglyTypedProgressGuide?.guide === 'like-10-and-follow-7') { 88 maybeWronglyTypedProgressGuide.numLikes = 89 Number(maybeWronglyTypedProgressGuide.numLikes) || 0 90 maybeWronglyTypedProgressGuide.numFollows = 91 Number(maybeWronglyTypedProgressGuide.numFollows) || 0 92 } else if (maybeWronglyTypedProgressGuide?.guide === 'follow-10') { 93 maybeWronglyTypedProgressGuide.numFollows = 94 Number(maybeWronglyTypedProgressGuide.numFollows) || 0 95 } 96 97 return maybeWronglyTypedProgressGuide 98 }, [isPending, variables, preferences]) 99 100 const [localGuideState, setLocalGuideState] = 101 React.useState<ProgressGuide>(undefined) 102 103 if (activeProgressGuide && !localGuideState) { 104 // hydrate from the server if needed 105 setLocalGuideState(activeProgressGuide) 106 } 107 108 const firstLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) 109 const fifthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) 110 const tenthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) 111 112 const fifthFollowToastRef = React.useRef<ProgressGuideToastRef | null>(null) 113 const tenthFollowToastRef = React.useRef<ProgressGuideToastRef | null>(null) 114 115 const controls = React.useMemo(() => { 116 return { 117 startProgressGuide(guide: ProgressGuideName) { 118 if (guide === 'like-10-and-follow-7') { 119 const guideObj = { 120 guide: 'like-10-and-follow-7', 121 numLikes: 0, 122 numFollows: 0, 123 isComplete: false, 124 } satisfies ProgressGuide 125 setLocalGuideState(guideObj) 126 mutateAsync(guideObj) 127 } else if (guide === 'follow-10') { 128 const guideObj = { 129 guide: 'follow-10', 130 numFollows: 0, 131 isComplete: false, 132 } satisfies ProgressGuide 133 setLocalGuideState(guideObj) 134 mutateAsync(guideObj) 135 } 136 }, 137 138 endProgressGuide() { 139 setLocalGuideState(undefined) 140 mutateAsync(undefined) 141 logEvent('progressGuide:hide', {}) 142 }, 143 144 captureAction(action: ProgressGuideAction, count = 1) { 145 let guide = activeProgressGuide 146 if (!guide || guide?.isComplete) { 147 return 148 } 149 if (guide?.guide === 'like-10-and-follow-7') { 150 if (action === ProgressGuideAction.Like) { 151 guide = { 152 ...guide, 153 numLikes: (Number(guide.numLikes) || 0) + count, 154 } 155 if (guide.numLikes === 1) { 156 firstLikeToastRef.current?.open() 157 } 158 if (guide.numLikes === 5) { 159 fifthLikeToastRef.current?.open() 160 } 161 if (guide.numLikes === 10) { 162 tenthLikeToastRef.current?.open() 163 } 164 } 165 if (action === ProgressGuideAction.Follow) { 166 guide = { 167 ...guide, 168 numFollows: (Number(guide.numFollows) || 0) + count, 169 } 170 } 171 if (Number(guide.numLikes) >= 10 && Number(guide.numFollows) >= 7) { 172 guide = { 173 ...guide, 174 isComplete: true, 175 } 176 } 177 } else if (guide?.guide === 'follow-10') { 178 if (action === ProgressGuideAction.Follow) { 179 guide = { 180 ...guide, 181 numFollows: (Number(guide.numFollows) || 0) + count, 182 } 183 184 if (guide.numFollows === 5) { 185 fifthFollowToastRef.current?.open() 186 } 187 if (guide.numFollows === 10) { 188 tenthFollowToastRef.current?.open() 189 } 190 } 191 if (Number(guide.numFollows) >= 10) { 192 guide = { 193 ...guide, 194 isComplete: true, 195 } 196 } 197 } 198 199 setLocalGuideState(guide) 200 mutateAsync(guide?.isComplete ? undefined : guide) 201 }, 202 } 203 }, [activeProgressGuide, mutateAsync, setLocalGuideState]) 204 205 return ( 206 <ProgressGuideContext.Provider value={localGuideState}> 207 <ProgressGuideControlContext.Provider value={controls}> 208 {children} 209 {localGuideState?.guide === 'like-10-and-follow-7' && ( 210 <> 211 <ProgressGuideToast 212 ref={firstLikeToastRef} 213 title={_(msg`Your first like!`)} 214 subtitle={_(msg`Like 10 posts to train the Discover feed`)} 215 /> 216 <ProgressGuideToast 217 ref={fifthLikeToastRef} 218 title={_(msg`Half way there!`)} 219 subtitle={_(msg`Like 10 posts to train the Discover feed`)} 220 /> 221 <ProgressGuideToast 222 ref={tenthLikeToastRef} 223 title={_(msg`Task complete - 10 likes!`)} 224 subtitle={_(msg`The Discover feed now knows what you like`)} 225 /> 226 <ProgressGuideToast 227 ref={fifthFollowToastRef} 228 title={_(msg`Half way there!`)} 229 subtitle={_(msg`Follow 10 accounts`)} 230 /> 231 <ProgressGuideToast 232 ref={tenthFollowToastRef} 233 title={_(msg`Task complete - 10 follows!`)} 234 subtitle={_(msg`You've found some people to follow`)} 235 /> 236 </> 237 )} 238 </ProgressGuideControlContext.Provider> 239 </ProgressGuideContext.Provider> 240 ) 241}