my fork of the bluesky client

[Statsig] Onboarding and routing events (#3302)

authored by danabra.mov and committed by

GitHub 396d183d ad3dd9f6

+259 -178
+62 -58
src/Navigation.tsx
··· 1 1 import * as React from 'react' 2 + import {JSX} from 'react/jsx-runtime' 3 + import {i18n, MessageDescriptor} from '@lingui/core' 4 + import {msg} from '@lingui/macro' 2 5 import { 3 - NavigationContainer, 4 - createNavigationContainerRef, 6 + BottomTabBarProps, 7 + createBottomTabNavigator, 8 + } from '@react-navigation/bottom-tabs' 9 + import { 5 10 CommonActions, 11 + createNavigationContainerRef, 12 + DarkTheme, 13 + DefaultTheme, 14 + NavigationContainer, 6 15 StackActions, 7 - DefaultTheme, 8 - DarkTheme, 9 16 } from '@react-navigation/native' 17 + 18 + import {timeout} from 'lib/async/timeout' 19 + import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' 20 + import {usePalette} from 'lib/hooks/usePalette' 21 + import {buildStateObject} from 'lib/routes/helpers' 10 22 import { 11 - BottomTabBarProps, 12 - createBottomTabNavigator, 13 - } from '@react-navigation/bottom-tabs' 14 - import { 15 - HomeTabNavigatorParams, 16 - SearchTabNavigatorParams, 23 + AllNavigatorParams, 24 + BottomTabNavigatorParams, 17 25 FeedsTabNavigatorParams, 18 - NotificationsTabNavigatorParams, 19 26 FlatNavigatorParams, 20 - AllNavigatorParams, 27 + HomeTabNavigatorParams, 21 28 MyProfileTabNavigatorParams, 22 - BottomTabNavigatorParams, 29 + NotificationsTabNavigatorParams, 30 + SearchTabNavigatorParams, 23 31 } from 'lib/routes/types' 24 - import {BottomBar} from './view/shell/bottom-bar/BottomBar' 25 - import {buildStateObject} from 'lib/routes/helpers' 26 - import {State, RouteParams} from 'lib/routes/types' 32 + import {RouteParams, State} from 'lib/routes/types' 33 + import {bskyTitle} from 'lib/strings/headings' 27 34 import {isAndroid, isNative} from 'platform/detection' 28 - import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' 35 + import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' 36 + import {AppPasswords} from 'view/screens/AppPasswords' 37 + import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' 38 + import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts' 39 + import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed' 40 + import {PreferencesThreads} from 'view/screens/PreferencesThreads' 41 + import {SavedFeeds} from 'view/screens/SavedFeeds' 42 + import HashtagScreen from '#/screens/Hashtag' 43 + import {ModerationScreen} from '#/screens/Moderation' 44 + import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' 45 + import {init as initAnalytics} from './lib/analytics/analytics' 46 + import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' 47 + import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig' 29 48 import {router} from './routes' 30 - import {usePalette} from 'lib/hooks/usePalette' 31 - import {bskyTitle} from 'lib/strings/headings' 32 - import {JSX} from 'react/jsx-runtime' 33 - import {timeout} from 'lib/async/timeout' 49 + import {useModalControls} from './state/modals' 34 50 import {useUnreadNotifications} from './state/queries/notifications/unread' 35 51 import {useSession} from './state/session' 36 - import {useModalControls} from './state/modals' 37 52 import { 53 + setEmailConfirmationRequested, 38 54 shouldRequestEmailConfirmation, 39 - setEmailConfirmationRequested, 40 55 } from './state/shell/reminders' 41 - import {init as initAnalytics} from './lib/analytics/analytics' 42 - import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' 43 - 56 + import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' 57 + import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy' 58 + import {DebugModScreen} from './view/screens/DebugMod' 59 + import {FeedsScreen} from './view/screens/Feeds' 44 60 import {HomeScreen} from './view/screens/Home' 45 - import {SearchScreen} from './view/screens/Search' 46 - import {FeedsScreen} from './view/screens/Feeds' 47 - import {NotificationsScreen} from './view/screens/Notifications' 61 + import {LanguageSettingsScreen} from './view/screens/LanguageSettings' 48 62 import {ListsScreen} from './view/screens/Lists' 49 - import {ModerationScreen} from '#/screens/Moderation' 63 + import {LogScreen} from './view/screens/Log' 50 64 import {ModerationModlistsScreen} from './view/screens/ModerationModlists' 51 65 import {NotFoundScreen} from './view/screens/NotFound' 52 - import {SettingsScreen} from './view/screens/Settings' 53 - import {LanguageSettingsScreen} from './view/screens/LanguageSettings' 66 + import {NotificationsScreen} from './view/screens/Notifications' 67 + import {PostLikedByScreen} from './view/screens/PostLikedBy' 68 + import {PostRepostedByScreen} from './view/screens/PostRepostedBy' 69 + import {PostThreadScreen} from './view/screens/PostThread' 70 + import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy' 54 71 import {ProfileScreen} from './view/screens/Profile' 72 + import {ProfileFeedScreen} from './view/screens/ProfileFeed' 73 + import {ProfileFeedLikedByScreen} from './view/screens/ProfileFeedLikedBy' 55 74 import {ProfileFollowersScreen} from './view/screens/ProfileFollowers' 56 75 import {ProfileFollowsScreen} from './view/screens/ProfileFollows' 57 - import {ProfileFeedScreen} from './view/screens/ProfileFeed' 58 - import {ProfileFeedLikedByScreen} from './view/screens/ProfileFeedLikedBy' 59 76 import {ProfileListScreen} from './view/screens/ProfileList' 60 - import {PostThreadScreen} from './view/screens/PostThread' 61 - import {PostLikedByScreen} from './view/screens/PostLikedBy' 62 - import {PostRepostedByScreen} from './view/screens/PostRepostedBy' 77 + import {SearchScreen} from './view/screens/Search' 78 + import {SettingsScreen} from './view/screens/Settings' 63 79 import {Storybook} from './view/screens/Storybook' 64 - import {DebugModScreen} from './view/screens/DebugMod' 65 - import {LogScreen} from './view/screens/Log' 66 80 import {SupportScreen} from './view/screens/Support' 67 - import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy' 68 81 import {TermsOfServiceScreen} from './view/screens/TermsOfService' 69 - import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' 70 - import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy' 71 - import {AppPasswords} from 'view/screens/AppPasswords' 72 - import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts' 73 - import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' 74 - import {SavedFeeds} from 'view/screens/SavedFeeds' 75 - import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed' 76 - import {PreferencesThreads} from 'view/screens/PreferencesThreads' 77 - import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' 82 + import {BottomBar} from './view/shell/bottom-bar/BottomBar' 78 83 import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' 79 - import {msg} from '@lingui/macro' 80 - import {i18n, MessageDescriptor} from '@lingui/core' 81 - import HashtagScreen from '#/screens/Hashtag' 82 - import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' 83 - import {logEvent, attachRouteToLogEvents} from './lib/statsig/statsig' 84 84 85 85 const navigationRef = createNavigationContainerRef<AllNavigatorParams>() 86 86 ··· 554 554 ref={navigationRef} 555 555 linking={LINKING} 556 556 theme={theme} 557 + onStateChange={() => { 558 + logEvent('router:navigate', {}) 559 + }} 557 560 onReady={() => { 558 561 attachRouteToLogEvents(getCurrentRouteName) 559 562 logModuleInitTime() 560 563 onReady() 564 + logEvent('router:navigate', {}) 561 565 }}> 562 566 {children} 563 567 </NavigationContainer> ··· 693 697 } 694 698 695 699 export { 700 + FlatNavigator, 701 + handleLink, 696 702 navigate, 697 - resetToTab, 698 703 reset, 699 - handleLink, 700 - TabsNavigator, 701 - FlatNavigator, 704 + resetToTab, 702 705 RoutesContainer, 706 + TabsNavigator, 703 707 }
+34
src/lib/statsig/events.ts
··· 1 1 export type LogEvents = { 2 + // App events 2 3 init: { 3 4 initMs: number 4 5 } ··· 14 15 secondsActive: number 15 16 } 16 17 'state:foreground': {} 18 + 'router:navigate': {} 19 + 20 + // Screen events 21 + 'splash:signInPressed': {} 22 + 'splash:createAccountPressed': {} 23 + 'signup:nextPressed': { 24 + activeStep: number 25 + } 26 + 'onboarding:interests:nextPressed': { 27 + selectedInterests: string[] 28 + selectedInterestsLength: number 29 + } 30 + 'onboarding:suggestedAccounts:nextPressed': { 31 + selectedAccountsLength: number 32 + skipped: boolean 33 + } 34 + 'onboarding:followingFeed:nextPressed': {} 35 + 'onboarding:algoFeeds:nextPressed': { 36 + selectedPrimaryFeeds: string[] 37 + selectedPrimaryFeedsLength: number 38 + selectedSecondaryFeeds: string[] 39 + selectedSecondaryFeedsLength: number 40 + } 41 + 'onboarding:topicalFeeds:nextPressed': { 42 + selectedFeeds: string[] 43 + selectedFeedsLength: number 44 + } 45 + 'onboarding:moderation:nextPressed': {} 46 + 'onboarding:finished:nextPressed': {} 17 47 'feed:endReached': { 18 48 feedType: string 19 49 itemCount: number ··· 22 52 feedType: string 23 53 reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest' 24 54 } 55 + 56 + // Data events 57 + 'account:create:begin': {} 58 + 'account:create:success': {} 25 59 'post:create': { 26 60 imageCount: number 27 61 isReply: boolean
+10 -3
src/lib/statsig/statsig.tsx
··· 1 1 import React from 'react' 2 2 import {Platform} from 'react-native' 3 + import {AppState, AppStateStatus} from 'react-native' 4 + import {sha256} from 'js-sha256' 3 5 import { 4 6 Statsig, 5 7 StatsigProvider, 6 8 useGate as useStatsigGate, 7 9 } from 'statsig-react-native-expo' 8 - import {AppState, AppStateStatus} from 'react-native' 10 + 9 11 import {useSession} from '../../state/session' 10 - import {sha256} from 'js-sha256' 11 12 import {LogEvents} from './events' 12 13 13 14 export type {LogEvents} ··· 24 25 25 26 type FlatJSONRecord = Record< 26 27 string, 27 - string | number | boolean | null | undefined 28 + | string 29 + | number 30 + | boolean 31 + | null 32 + | undefined 33 + // Technically not scalar but Statsig will stringify it which works for us: 34 + | string[] 28 35 > 29 36 30 37 let getCurrentRouteName: () => string | null | undefined = () => null
+18 -12
src/screens/Onboarding/StepAlgoFeeds/index.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {useLingui} from '@lingui/react' 4 3 import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 5 6 - import {IS_PROD} from '#/env' 7 - import {atoms as a, tokens, useTheme} from '#/alf' 8 - import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 9 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 - import * as Toggle from '#/components/forms/Toggle' 11 - import {Text} from '#/components/Typography' 12 - import {Loader} from '#/components/Loader' 13 - import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' 14 6 import {useAnalytics} from '#/lib/analytics/analytics' 15 - 16 - import {Context} from '#/screens/Onboarding/state' 7 + import {logEvent} from '#/lib/statsig/statsig' 17 8 import { 18 - Title, 19 9 Description, 20 10 OnboardingControls, 11 + Title, 21 12 } from '#/screens/Onboarding/Layout' 13 + import {Context} from '#/screens/Onboarding/state' 22 14 import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard' 15 + import {atoms as a, tokens, useTheme} from '#/alf' 16 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 17 + import * as Toggle from '#/components/forms/Toggle' 23 18 import {IconCircle} from '#/components/IconCircle' 19 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 20 + import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' 21 + import {Loader} from '#/components/Loader' 22 + import {Text} from '#/components/Typography' 23 + import {IS_PROD} from '#/env' 24 24 25 25 export type FeedConfig = { 26 26 default: boolean ··· 84 84 setSaving(false) 85 85 dispatch({type: 'next'}) 86 86 track('OnboardingV2:StepAlgoFeeds:End', { 87 + selectedPrimaryFeeds: primaryFeedUris, 88 + selectedPrimaryFeedsLength: primaryFeedUris.length, 89 + selectedSecondaryFeeds: secondaryFeedUris, 90 + selectedSecondaryFeedsLength: secondaryFeedUris.length, 91 + }) 92 + logEvent('onboarding:algoFeeds:nextPressed', { 87 93 selectedPrimaryFeeds: primaryFeedUris, 88 94 selectedPrimaryFeedsLength: primaryFeedUris.length, 89 95 selectedSecondaryFeeds: secondaryFeedUris,
+16 -15
src/screens/Onboarding/StepFinished.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {useLingui} from '@lingui/react' 4 3 import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 5 6 + import {useAnalytics} from '#/lib/analytics/analytics' 7 + import {logEvent} from '#/lib/statsig/statsig' 6 8 import {logger} from '#/logger' 7 - import {atoms as a, useTheme} from '#/alf' 8 - import {Button, ButtonText, ButtonIcon} from '#/components/Button' 9 - import {News2_Stroke2_Corner0_Rounded as News} from '#/components/icons/News2' 10 - import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 11 - import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 12 - import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' 13 - import {Text} from '#/components/Typography' 14 - import {useOnboardingDispatch} from '#/state/shell' 15 - import {Loader} from '#/components/Loader' 16 9 import {useSetSaveFeedsMutation} from '#/state/queries/preferences' 17 10 import {getAgent} from '#/state/session' 18 - import {useAnalytics} from '#/lib/analytics/analytics' 19 - 20 - import {Context} from '#/screens/Onboarding/state' 11 + import {useOnboardingDispatch} from '#/state/shell' 21 12 import { 22 - Title, 23 13 Description, 24 14 OnboardingControls, 15 + Title, 25 16 } from '#/screens/Onboarding/Layout' 26 - import {IconCircle} from '#/components/IconCircle' 17 + import {Context} from '#/screens/Onboarding/state' 27 18 import { 28 19 bulkWriteFollows, 29 20 sortPrimaryAlgorithmFeeds, 30 21 } from '#/screens/Onboarding/util' 22 + import {atoms as a, useTheme} from '#/alf' 23 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 24 + import {IconCircle} from '#/components/IconCircle' 25 + import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 26 + import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 27 + import {News2_Stroke2_Corner0_Rounded as News} from '#/components/icons/News2' 28 + import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/Trending2' 29 + import {Loader} from '#/components/Loader' 30 + import {Text} from '#/components/Typography' 31 31 32 32 export function StepFinished() { 33 33 const {_} = useLingui() ··· 76 76 onboardDispatch({type: 'finish'}) 77 77 track('OnboardingV2:StepFinished:End') 78 78 track('OnboardingV2:Complete') 79 + logEvent('onboarding:finished:nextPressed', {}) 79 80 }, [state, dispatch, onboardDispatch, setSaving, saveFeeds, track]) 80 81 81 82 React.useEffect(() => {
+16 -15
src/screens/Onboarding/StepFollowingFeed.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {useLingui} from '@lingui/react' 4 3 import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 5 6 - import {atoms as a} from '#/alf' 7 - import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 8 - import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 9 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 - import {Text} from '#/components/Typography' 11 - import {Divider} from '#/components/Divider' 12 - import * as Toggle from '#/components/forms/Toggle' 13 6 import {useAnalytics} from '#/lib/analytics/analytics' 14 - 15 - import {Context} from '#/screens/Onboarding/state' 7 + import {logEvent} from '#/lib/statsig/statsig' 8 + import { 9 + usePreferencesQuery, 10 + useSetFeedViewPreferencesMutation, 11 + } from 'state/queries/preferences' 16 12 import { 17 - Title, 18 13 Description, 19 14 OnboardingControls, 15 + Title, 20 16 } from '#/screens/Onboarding/Layout' 21 - import { 22 - usePreferencesQuery, 23 - useSetFeedViewPreferencesMutation, 24 - } from 'state/queries/preferences' 17 + import {Context} from '#/screens/Onboarding/state' 18 + import {atoms as a} from '#/alf' 19 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 20 + import {Divider} from '#/components/Divider' 21 + import * as Toggle from '#/components/forms/Toggle' 25 22 import {IconCircle} from '#/components/IconCircle' 23 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 24 + import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 25 + import {Text} from '#/components/Typography' 26 26 27 27 export function StepFollowingFeed() { 28 28 const {_} = useLingui() ··· 46 46 const onContinue = React.useCallback(() => { 47 47 dispatch({type: 'next'}) 48 48 track('OnboardingV2:StepFollowingFeed:End') 49 + logEvent('onboarding:followingFeed:nextPressed', {}) 49 50 }, [track, dispatch]) 50 51 51 52 React.useEffect(() => {
+19 -15
src/screens/Onboarding/StepInterests/index.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {useLingui} from '@lingui/react' 4 3 import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 5 import {useQuery} from '@tanstack/react-query' 6 6 7 + import {useAnalytics} from '#/lib/analytics/analytics' 8 + import {logEvent} from '#/lib/statsig/statsig' 9 + import {capitalize} from '#/lib/strings/capitalize' 7 10 import {logger} from '#/logger' 8 - import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 - import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 10 - import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' 11 - import {EmojiSad_Stroke2_Corner0_Rounded as EmojiSad} from '#/components/icons/Emoji' 12 - import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwise} from '#/components/icons/ArrowRotateCounterClockwise' 13 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 - import {Loader} from '#/components/Loader' 15 - import * as Toggle from '#/components/forms/Toggle' 16 11 import {getAgent} from '#/state/session' 17 - import {useAnalytics} from '#/lib/analytics/analytics' 18 - import {Text} from '#/components/Typography' 19 12 import {useOnboardingDispatch} from '#/state/shell' 20 - import {capitalize} from '#/lib/strings/capitalize' 21 - 22 - import {Context, ApiResponseMap} from '#/screens/Onboarding/state' 23 13 import { 24 - Title, 25 14 Description, 26 15 OnboardingControls, 16 + Title, 27 17 } from '#/screens/Onboarding/Layout' 18 + import {ApiResponseMap, Context} from '#/screens/Onboarding/state' 28 19 import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton' 20 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 21 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 22 + import * as Toggle from '#/components/forms/Toggle' 29 23 import {IconCircle} from '#/components/IconCircle' 24 + import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwise} from '#/components/icons/ArrowRotateCounterClockwise' 25 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 26 + import {EmojiSad_Stroke2_Corner0_Rounded as EmojiSad} from '#/components/icons/Emoji' 27 + import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' 28 + import {Loader} from '#/components/Loader' 29 + import {Text} from '#/components/Typography' 30 30 31 31 export function StepInterests() { 32 32 const {_} = useLingui() ··· 104 104 dispatch({type: 'next'}) 105 105 106 106 track('OnboardingV2:StepInterests:End', { 107 + selectedInterests: interests, 108 + selectedInterestsLength: interests.length, 109 + }) 110 + logEvent('onboarding:interests:nextPressed', { 107 111 selectedInterests: interests, 108 112 selectedInterestsLength: interests.length, 109 113 })
+13 -12
src/screens/Onboarding/StepModeration/index.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {useLingui} from '@lingui/react' 3 + import {LABELS} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 - import {LABELS} from '@atproto/api' 5 + import {useLingui} from '@lingui/react' 6 6 7 - import {atoms as a} from '#/alf' 8 - import {usePreferencesSetAdultContentMutation} from 'state/queries/preferences' 9 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 - import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 11 - import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 12 - import {usePreferencesQuery} from '#/state/queries/preferences' 13 - import {Loader} from '#/components/Loader' 14 7 import {useAnalytics} from '#/lib/analytics/analytics' 15 - 8 + import {logEvent} from '#/lib/statsig/statsig' 9 + import {usePreferencesQuery} from '#/state/queries/preferences' 10 + import {usePreferencesSetAdultContentMutation} from 'state/queries/preferences' 16 11 import { 17 12 Description, 18 13 OnboardingControls, 19 14 Title, 20 15 } from '#/screens/Onboarding/Layout' 21 - import {ModerationOption} from '#/screens/Onboarding/StepModeration/ModerationOption' 16 + import {Context} from '#/screens/Onboarding/state' 22 17 import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/AdultContentEnabledPref' 23 - import {Context} from '#/screens/Onboarding/state' 18 + import {ModerationOption} from '#/screens/Onboarding/StepModeration/ModerationOption' 19 + import {atoms as a} from '#/alf' 20 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 24 21 import {IconCircle} from '#/components/IconCircle' 22 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 23 + import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 24 + import {Loader} from '#/components/Loader' 25 25 26 26 export function StepModeration() { 27 27 const {_} = useLingui() ··· 45 45 const onContinue = React.useCallback(() => { 46 46 dispatch({type: 'next'}) 47 47 track('OnboardingV2:StepModeration:End') 48 + logEvent('onboarding:moderation:nextPressed', {}) 48 49 }, [track, dispatch]) 49 50 50 51 React.useEffect(() => {
+21 -13
src/screens/Onboarding/StepSuggestedAccounts/index.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import {AppBskyActorDefs} from '@atproto/api' 4 - import {useLingui} from '@lingui/react' 5 4 import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 6 7 - import {atoms as a, useBreakpoints} from '#/alf' 8 - import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 9 - import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' 10 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 - import {Text} from '#/components/Typography' 12 - import {useProfilesQuery} from '#/state/queries/profile' 13 - import {Loader} from '#/components/Loader' 14 - import * as Toggle from '#/components/forms/Toggle' 15 - import {useModerationOpts} from '#/state/queries/preferences' 16 7 import {useAnalytics} from '#/lib/analytics/analytics' 8 + import {logEvent} from '#/lib/statsig/statsig' 17 9 import {capitalize} from '#/lib/strings/capitalize' 18 - 19 - import {Context} from '#/screens/Onboarding/state' 10 + import {useModerationOpts} from '#/state/queries/preferences' 11 + import {useProfilesQuery} from '#/state/queries/profile' 20 12 import { 21 - Title, 22 13 Description, 23 14 OnboardingControls, 15 + Title, 24 16 } from '#/screens/Onboarding/Layout' 17 + import {Context} from '#/screens/Onboarding/state' 25 18 import { 26 19 SuggestedAccountCard, 27 20 SuggestedAccountCardPlaceholder, 28 21 } from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard' 29 22 import {aggregateInterestItems} from '#/screens/Onboarding/util' 23 + import {atoms as a, useBreakpoints} from '#/alf' 24 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 25 + import * as Toggle from '#/components/forms/Toggle' 30 26 import {IconCircle} from '#/components/IconCircle' 27 + import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' 28 + import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 29 + import {Loader} from '#/components/Loader' 30 + import {Text} from '#/components/Typography' 31 31 32 32 export function Inner({ 33 33 profiles, ··· 110 110 track('OnboardingV2:StepSuggestedAccounts:End', { 111 111 selectedAccountsLength: dids.length, 112 112 }) 113 + logEvent('onboarding:suggestedAccounts:nextPressed', { 114 + selectedAccountsLength: dids.length, 115 + skipped: false, 116 + }) 113 117 }, [dids, setSaving, dispatch, track]) 114 118 115 119 const handleSkip = React.useCallback(() => { 116 120 // if a user comes back and clicks skip, erase follows 117 121 dispatch({type: 'setSuggestedAccountsStepResults', accountDids: []}) 118 122 dispatch({type: 'next'}) 123 + logEvent('onboarding:suggestedAccounts:nextPressed', { 124 + selectedAccountsLength: 0, 125 + skipped: true, 126 + }) 119 127 }, [dispatch]) 120 128 121 129 const isLoading = isProfilesLoading && moderationOpts
+16 -12
src/screens/Onboarding/StepTopicalFeeds.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {useLingui} from '@lingui/react' 4 3 import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 5 6 - import {atoms as a} from '#/alf' 7 - import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 8 - import {ListMagnifyingGlass_Stroke2_Corner0_Rounded as ListMagnifyingGlass} from '#/components/icons/ListMagnifyingGlass' 9 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 - import * as Toggle from '#/components/forms/Toggle' 11 - import {Loader} from '#/components/Loader' 12 6 import {useAnalytics} from '#/lib/analytics/analytics' 7 + import {logEvent} from '#/lib/statsig/statsig' 13 8 import {capitalize} from '#/lib/strings/capitalize' 14 - 15 - import {Context} from '#/screens/Onboarding/state' 9 + import {IS_TEST_USER} from 'lib/constants' 10 + import {useSession} from 'state/session' 16 11 import { 17 - Title, 18 12 Description, 19 13 OnboardingControls, 14 + Title, 20 15 } from '#/screens/Onboarding/Layout' 16 + import {Context} from '#/screens/Onboarding/state' 21 17 import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard' 22 18 import {aggregateInterestItems} from '#/screens/Onboarding/util' 19 + import {atoms as a} from '#/alf' 20 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 21 + import * as Toggle from '#/components/forms/Toggle' 23 22 import {IconCircle} from '#/components/IconCircle' 24 - import {IS_TEST_USER} from 'lib/constants' 25 - import {useSession} from 'state/session' 23 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 24 + import {ListMagnifyingGlass_Stroke2_Corner0_Rounded as ListMagnifyingGlass} from '#/components/icons/ListMagnifyingGlass' 25 + import {Loader} from '#/components/Loader' 26 26 27 27 export function StepTopicalFeeds() { 28 28 const {_} = useLingui() ··· 59 59 setSaving(false) 60 60 dispatch({type: 'next'}) 61 61 track('OnboardingV2:StepTopicalFeeds:End', { 62 + selectedFeeds: selectedFeedUris, 63 + selectedFeedsLength: selectedFeedUris.length, 64 + }) 65 + logEvent('onboarding:topicalFeeds:nextPressed', { 62 66 selectedFeeds: selectedFeedUris, 63 67 selectedFeedsLength: selectedFeedUris.length, 64 68 })
+4
src/screens/Signup/index.tsx
··· 5 5 6 6 import {useAnalytics} from '#/lib/analytics/analytics' 7 7 import {FEEDBACK_FORM_URL} from '#/lib/constants' 8 + import {logEvent} from '#/lib/statsig/statsig' 8 9 import {createFullHandle} from '#/lib/strings/handles' 9 10 import {useServiceQuery} from '#/state/queries/service' 10 11 import {getAgent} from '#/state/session' ··· 99 100 } 100 101 101 102 dispatch({type: 'next'}) 103 + logEvent('signup:nextPressed', { 104 + activeStep: state.activeStep, 105 + }) 102 106 }, [ 103 107 _, 104 108 state.activeStep,
+10 -8
src/state/session/index.tsx
··· 1 1 import React from 'react' 2 2 import { 3 - BskyAgent, 4 3 AtpPersistSessionHandler, 5 4 BSKY_LABELER_DID, 5 + BskyAgent, 6 6 } from '@atproto/api' 7 7 import {useQueryClient} from '@tanstack/react-query' 8 8 import {jwtDecode} from 'jwt-decode' 9 9 10 - import {IS_DEV} from '#/env' 11 - import {IS_TEST_USER} from '#/lib/constants' 12 - import {isWeb} from '#/platform/detection' 10 + import {track} from '#/lib/analytics/analytics' 13 11 import {networkRetry} from '#/lib/async/retry' 12 + import {IS_TEST_USER} from '#/lib/constants' 13 + import {logEvent, LogEvents} from '#/lib/statsig/statsig' 14 + import {hasProp} from '#/lib/type-guards' 14 15 import {logger} from '#/logger' 16 + import {isWeb} from '#/platform/detection' 15 17 import * as persisted from '#/state/persisted' 16 18 import {PUBLIC_BSKY_AGENT} from '#/state/queries' 17 - import {emitSessionDropped} from '../events' 18 19 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 19 20 import {useCloseAllActiveElements} from '#/state/util' 20 - import {track} from '#/lib/analytics/analytics' 21 - import {hasProp} from '#/lib/type-guards' 21 + import {IS_DEV} from '#/env' 22 + import {emitSessionDropped} from '../events' 22 23 import {readLabelers} from './agent-config' 23 - import {logEvent, LogEvents} from '#/lib/statsig/statsig' 24 24 25 25 let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT 26 26 ··· 230 230 }: any) => { 231 231 logger.info(`session: creating account`) 232 232 track('Try Create Account') 233 + logEvent('account:create:begin', {}) 233 234 234 235 const agent = new BskyAgent({service}) 235 236 ··· 290 291 291 292 logger.debug(`session: created account`, {}, logger.DebugContext.session) 292 293 track('Create Account') 294 + logEvent('account:create:success', {}) 293 295 }, 294 296 [upsertAccount, queryClient, clearCurrentAccount], 295 297 )
+20 -15
src/view/com/auth/LoggedOut.tsx
··· 1 1 import React from 'react' 2 - import {View, Pressable} from 'react-native' 2 + import {Pressable, View} from 'react-native' 3 3 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 + import {msg, Trans} from '@lingui/macro' 4 5 import {useLingui} from '@lingui/react' 5 - import {Trans, msg} from '@lingui/macro' 6 6 import {useNavigation} from '@react-navigation/native' 7 7 8 - import {isIOS, isNative} from '#/platform/detection' 9 - import {Login} from '#/screens/Login' 10 - import {Signup} from '#/screens/Signup' 11 - import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 12 - import {s} from '#/lib/styles' 8 + import {useAnalytics} from '#/lib/analytics/analytics' 13 9 import {usePalette} from '#/lib/hooks/usePalette' 14 - import {useAnalytics} from '#/lib/analytics/analytics' 15 - import {SplashScreen} from './SplashScreen' 16 - import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' 17 10 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 11 + import {logEvent} from '#/lib/statsig/statsig' 12 + import {s} from '#/lib/styles' 13 + import {isIOS, isNative} from '#/platform/detection' 14 + import {useSession} from '#/state/session' 18 15 import { 19 16 useLoggedOutView, 20 17 useLoggedOutViewControls, 21 18 } from '#/state/shell/logged-out' 22 - import {useSession} from '#/state/session' 23 - import {Text} from '#/view/com/util/text/Text' 19 + import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' 24 20 import {NavigationProp} from 'lib/routes/types' 21 + import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 22 + import {Text} from '#/view/com/util/text/Text' 23 + import {Login} from '#/screens/Login' 24 + import {Signup} from '#/screens/Signup' 25 + import {SplashScreen} from './SplashScreen' 25 26 26 27 enum ScreenState { 27 28 S_LoginOrCreateAccount, ··· 133 134 134 135 {screenState === ScreenState.S_LoginOrCreateAccount ? ( 135 136 <SplashScreen 136 - onPressSignin={() => setScreenState(ScreenState.S_Login)} 137 - onPressCreateAccount={() => 137 + onPressSignin={() => { 138 + setScreenState(ScreenState.S_Login) 139 + logEvent('splash:signInPressed', {}) 140 + }} 141 + onPressCreateAccount={() => { 138 142 setScreenState(ScreenState.S_CreateAccount) 139 - } 143 + logEvent('splash:createAccountPressed', {}) 144 + }} 140 145 /> 141 146 ) : undefined} 142 147 {screenState === ScreenState.S_Login ? (