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