mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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}