Something went wrong. Try again.
Bluesky app fork with some witchin' additions 💫 forked from jollywhoppers.com/witchsky.app
Something went wrong. Try again.
7.9 kB · 244 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245import React, {useMemo} from 'react'import {msg} from '@lingui/macro'import {useLingui} from '@lingui/react'
import { ProgressGuideToast, type ProgressGuideToastRef,} from '#/components/ProgressGuide/Toast'import {useAnalytics} from '#/analytics'import { usePreferencesQuery, useSetActiveProgressGuideMutation,} from '../queries/preferences'
export enum ProgressGuideAction { Like = 'like', Follow = 'follow',}
type ProgressGuideName = 'like-10-and-follow-7' | 'follow-10'
/** * Progress Guides that extend this interface must specify their name in the `guide` field, so it can be used as a discriminated union */interface BaseProgressGuide { guide: ProgressGuideName isComplete: boolean [key: string]: any}
export interface Like10AndFollow7ProgressGuide extends BaseProgressGuide { guide: 'like-10-and-follow-7' numLikes: number numFollows: number}
export interface Follow10ProgressGuide extends BaseProgressGuide { guide: 'follow-10' numFollows: number}
export type ProgressGuide = | Like10AndFollow7ProgressGuide | Follow10ProgressGuide | undefined
const ProgressGuideContext = React.createContext<ProgressGuide>(undefined)ProgressGuideContext.displayName = 'ProgressGuideContext'
const ProgressGuideControlContext = React.createContext<{ startProgressGuide(guide: ProgressGuideName): void endProgressGuide(): void captureAction(action: ProgressGuideAction, count?: number): void}>({ startProgressGuide: (_guide: ProgressGuideName) => {}, endProgressGuide: () => {}, captureAction: (_action: ProgressGuideAction, _count = 1) => {},})ProgressGuideControlContext.displayName = 'ProgressGuideControlContext'
export function useProgressGuide(guide: ProgressGuideName) { const ctx = React.useContext(ProgressGuideContext) if (ctx?.guide === guide) { return ctx } return undefined}
export function useProgressGuideControls() { return React.useContext(ProgressGuideControlContext)}
export function Provider({children}: React.PropsWithChildren<{}>) { const ax = useAnalytics() const {_} = useLingui() const {data: preferences} = usePreferencesQuery() const {mutateAsync, variables, isPending} = useSetActiveProgressGuideMutation()
const activeProgressGuide = useMemo(() => { const rawProgressGuide = ( isPending ? variables : preferences?.bskyAppState?.activeProgressGuide ) as ProgressGuide
if (!rawProgressGuide) return undefined
// ensure the unspecced attributes have the correct types // clone then mutate const {...maybeWronglyTypedProgressGuide} = rawProgressGuide if (maybeWronglyTypedProgressGuide?.guide === 'like-10-and-follow-7') { maybeWronglyTypedProgressGuide.numLikes = Number(maybeWronglyTypedProgressGuide.numLikes) || 0 maybeWronglyTypedProgressGuide.numFollows = Number(maybeWronglyTypedProgressGuide.numFollows) || 0 } else if (maybeWronglyTypedProgressGuide?.guide === 'follow-10') { maybeWronglyTypedProgressGuide.numFollows = Number(maybeWronglyTypedProgressGuide.numFollows) || 0 }
return maybeWronglyTypedProgressGuide }, [isPending, variables, preferences])
const [localGuideState, setLocalGuideState] = React.useState<ProgressGuide>(undefined)
if (activeProgressGuide && !localGuideState) { // hydrate from the server if needed setLocalGuideState(activeProgressGuide) }
const firstLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) const fifthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null) const tenthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null)
const fifthFollowToastRef = React.useRef<ProgressGuideToastRef | null>(null) const tenthFollowToastRef = React.useRef<ProgressGuideToastRef | null>(null)
const controls = React.useMemo(() => { return { startProgressGuide(guide: ProgressGuideName) { if (guide === 'like-10-and-follow-7') { const guideObj = { guide: 'like-10-and-follow-7', numLikes: 0, numFollows: 0, isComplete: false, } satisfies ProgressGuide setLocalGuideState(guideObj) mutateAsync(guideObj) } else if (guide === 'follow-10') { const guideObj = { guide: 'follow-10', numFollows: 0, isComplete: false, } satisfies ProgressGuide setLocalGuideState(guideObj) mutateAsync(guideObj) } },
endProgressGuide() { setLocalGuideState(undefined) mutateAsync(undefined) ax.metric('progressGuide:hide', {}) },
captureAction(action: ProgressGuideAction, count = 1) { let guide = activeProgressGuide if (!guide || guide?.isComplete) { return } if (guide?.guide === 'like-10-and-follow-7') { if (action === ProgressGuideAction.Like) { guide = { ...guide, numLikes: (Number(guide.numLikes) || 0) + count, } if (guide.numLikes === 1) { firstLikeToastRef.current?.open() } if (guide.numLikes === 5) { fifthLikeToastRef.current?.open() } if (guide.numLikes === 10) { tenthLikeToastRef.current?.open() } } if (action === ProgressGuideAction.Follow) { guide = { ...guide, numFollows: (Number(guide.numFollows) || 0) + count, } } if (Number(guide.numLikes) >= 10 && Number(guide.numFollows) >= 7) { guide = { ...guide, isComplete: true, } } } else if (guide?.guide === 'follow-10') { if (action === ProgressGuideAction.Follow) { guide = { ...guide, numFollows: (Number(guide.numFollows) || 0) + count, }
if (guide.numFollows === 5) { fifthFollowToastRef.current?.open() } if (guide.numFollows === 10) { tenthFollowToastRef.current?.open() } } if (Number(guide.numFollows) >= 10) { guide = { ...guide, isComplete: true, } } }
setLocalGuideState(guide) mutateAsync(guide?.isComplete ? undefined : guide) }, } }, [ax, activeProgressGuide, mutateAsync, setLocalGuideState])
return ( <ProgressGuideContext.Provider value={localGuideState}> <ProgressGuideControlContext.Provider value={controls}> {children} {localGuideState?.guide === 'like-10-and-follow-7' && ( <> <ProgressGuideToast ref={firstLikeToastRef} title={_(msg`Your first like!`)} subtitle={_(msg`Like 10 posts to train the Discover feed`)} /> <ProgressGuideToast ref={fifthLikeToastRef} title={_(msg`Half way there!`)} subtitle={_(msg`Like 10 posts to train the Discover feed`)} /> <ProgressGuideToast ref={tenthLikeToastRef} title={_(msg`Task complete - 10 likes!`)} subtitle={_(msg`The Discover feed now knows what you like`)} /> <ProgressGuideToast ref={fifthFollowToastRef} title={_(msg`Half way there!`)} subtitle={_(msg`Follow 10 accounts`)} /> <ProgressGuideToast ref={tenthFollowToastRef} title={_(msg`Task complete - 10 follows!`)} subtitle={_(msg`You've found some people to follow`)} /> </> )} </ProgressGuideControlContext.Provider> </ProgressGuideContext.Provider> )}