diff --git a/eslint.config.mjs b/eslint.config.mjs index c3c36b4c4f..951d3442c5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -119,7 +119,6 @@ export default defineConfig( }, ], 'bsky-internal/use-exact-imports': 'error', - 'bsky-internal/use-typed-gates': 'error', 'bsky-internal/use-prefixed-imports': 'error', /** diff --git a/eslint/index.js b/eslint/index.js index 3cd7e61344..4d59c8c1d0 100644 --- a/eslint/index.js +++ b/eslint/index.js @@ -8,7 +8,6 @@ const plugin = { rules: { 'avoid-unwrapped-text': require('./avoid-unwrapped-text'), 'use-exact-imports': require('./use-exact-imports'), - 'use-typed-gates': require('./use-typed-gates'), 'use-prefixed-imports': require('./use-prefixed-imports'), }, } diff --git a/eslint/use-typed-gates.js b/eslint/use-typed-gates.js deleted file mode 100644 index 37969951e7..0000000000 --- a/eslint/use-typed-gates.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: - 'Enforce using internal statsig wrapper instead of npm package', - }, - schema: [], - }, - create(context) { - return { - ImportSpecifier(node) { - if ( - !node.local || - node.local.type !== 'Identifier' || - node.local.name !== 'useGate' - ) { - return - } - if ( - node.parent.type !== 'ImportDeclaration' || - !node.parent.source || - node.parent.source.type !== 'Literal' - ) { - return - } - const source = node.parent.source.value - if (source.startsWith('statsig') || source.startsWith('@statsig')) { - context.report({ - node, - message: - "Use useGate() from '#/lib/statsig/statsig' instead of the one on npm.", - }) - } - // TODO: Verify gate() call results aren't stored in variables. - }, - } - }, -} diff --git a/jest/jestSetup.js b/jest/jestSetup.js index ec5d4a9361..2b9ddf5348 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -99,19 +99,9 @@ jest.mock('expo-modules-core', () => ({ requireNativeViewManager: jest.fn().mockImplementation(_ => { return () => null }), + createPermissionHook: () => () => [true], })) jest.mock('expo-localization', () => ({ getLocales: () => [], })) - -jest.mock('statsig-react-native-expo', () => ({ - Statsig: { - initialize() {}, - initializeCalled() { - return false - }, - }, -})) - -jest.mock('../src/lib/statsig/statsig', () => ({})) diff --git a/package.json b/package.json index fc9d5ed714..2be25b505b 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,6 @@ "react-textarea-autosize": "^8.5.3", "sonner": "^2.0.7", "sonner-native": "^0.21.0", - "statsig-react-native-expo": "^4.6.1", "tippy.js": "^6.3.7", "tlds": "^1.234.0", "tldts": "^6.1.46", diff --git a/src/App.native.tsx b/src/App.native.tsx index fe57ba86d3..1568f7b8ae 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -17,7 +17,6 @@ import * as Sentry from '@sentry/react-native' import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController' import {Provider as HideBottomBarBorderProvider} from '#/lib/hooks/useHideBottomBarBorder' import {QueryProvider} from '#/lib/react-query' -import {Provider as StatsigProvider} from '#/lib/statsig/statsig' import {s} from '#/lib/styles' import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' @@ -153,53 +152,51 @@ function InnerApp() { - - - - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 0a632ccdbf..a0eff936f5 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -9,7 +9,6 @@ import {useLingui} from '@lingui/react' import * as Sentry from '@sentry/react-native' import {QueryProvider} from '#/lib/react-query' -import {Provider as StatsigProvider} from '#/lib/statsig/statsig' import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' @@ -132,49 +131,47 @@ function InnerApp() { - - - - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ageAssurance/components/NoAccessScreen.tsx b/src/ageAssurance/components/NoAccessScreen.tsx index ca5716a37a..9515b820e3 100644 --- a/src/ageAssurance/components/NoAccessScreen.tsx +++ b/src/ageAssurance/components/NoAccessScreen.tsx @@ -9,7 +9,6 @@ import { useCreateSupportLink, } from '#/lib/hooks/useCreateSupportLink' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' -import {logger} from '#/logger' import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' import {useSessionApi} from '#/state/session' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' @@ -36,8 +35,8 @@ import { isLegacyBirthdateBug, useAgeAssuranceRegionConfig, } from '#/ageAssurance/util' -import {IS_WEB} from '#/env' -import {IS_NATIVE} from '#/env' +import {useAnalytics} from '#/analytics' +import {IS_NATIVE, IS_WEB} from '#/env' import {useDeviceGeolocationApi} from '#/geolocation' const textStyles = [a.text_md, a.leading_snug] @@ -45,6 +44,7 @@ const textStyles = [a.text_md, a.leading_snug] export function NoAccessScreen() { const t = useTheme() const {_} = useLingui() + const ax = useAnalytics() const {gtPhone} = useBreakpoints() const insets = useSafeAreaInsets() const birthdateControl = useDialogControl() @@ -63,8 +63,8 @@ export function NoAccessScreen() { useEffect(() => { // just counting overall hits here - logger.metric(`blockedGeoOverlay:shown`, {}) - logger.metric(`ageAssurance:noAccessScreen:shown`, { + ax.metric(`blockedGeoOverlay:shown`, {}) + ax.metric(`ageAssurance:noAccessScreen:shown`, { accountCreatedAt: data?.accountCreatedAt || 'unknown', isAARegion, hasDeclaredAge, @@ -103,10 +103,7 @@ export function NoAccessScreen() { label={_(msg`Click here to update your birthdate`)} style={[textStyles]} {...createStaticClick(() => { - logger.metric( - 'ageAssurance:noAccessScreen:openBirthdateDialog', - {}, - ) + ax.metric('ageAssurance:noAccessScreen:openBirthdateDialog', {}) birthdateControl.open() })}> clicking here @@ -272,6 +269,7 @@ export function NoAccessScreen() { function AccessSection() { const t = useTheme() const {_, i18n} = useLingui() + const ax = useAnalytics() const control = useDialogControl() const appealControl = Dialog.useDialogControl() const locationControl = Dialog.useDialogControl() @@ -305,7 +303,7 @@ function AccessSection() { label={_(msg`Contact our moderation team`)} {...createStaticClick(() => { appealControl.open() - logger.metric('ageAssurance:appealDialogOpen', {}) + ax.metric('ageAssurance:appealDialogOpen', {}) })}> contact our moderation team {' '} @@ -321,7 +319,7 @@ function AccessSection() { color={hasInitiated ? 'secondary' : 'primary'} onPress={() => { control.open() - logger.metric('ageAssurance:initDialogOpen', { + ax.metric('ageAssurance:initDialogOpen', { hasInitiatedPreviously: hasInitiated, }) }}> diff --git a/src/ageAssurance/components/RedirectOverlay.tsx b/src/ageAssurance/components/RedirectOverlay.tsx index b7bc51df3c..dbd498447a 100644 --- a/src/ageAssurance/components/RedirectOverlay.tsx +++ b/src/ageAssurance/components/RedirectOverlay.tsx @@ -25,9 +25,8 @@ import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icon import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {refetchAgeAssuranceServerState} from '#/ageAssurance' -import {logger} from '#/ageAssurance' -import {IS_WEB} from '#/env' -import {IS_IOS} from '#/env' +import {useAnalytics} from '#/analytics' +import {IS_IOS, IS_WEB} from '#/env' export type RedirectOverlayState = { result: 'success' | 'unknown' @@ -174,6 +173,7 @@ export function RedirectOverlay() { function Inner() { const t = useTheme() + const ax = useAnalytics() const {_} = useLingui() const agent = useAgent() const polling = useRef(false) @@ -187,7 +187,7 @@ function Inner() { polling.current = true - logger.metric('ageAssurance:redirectDialogOpen', {}) + ax.metric('ageAssurance:redirectDialogOpen', {}) wait( 3e3, @@ -218,18 +218,18 @@ function Inner() { setSuccess(true) - logger.metric('ageAssurance:redirectDialogSuccess', {}) + ax.metric('ageAssurance:redirectDialogSuccess', {}) }) .catch(() => { if (unmounted.current) return setError(true) - logger.metric('ageAssurance:redirectDialogFail', {}) + ax.metric('ageAssurance:redirectDialogFail', {}) }) return () => { unmounted.current = true } - }, [agent]) + }, [ax, agent]) if (success) { return ( diff --git a/src/ageAssurance/useBeginAgeAssurance.ts b/src/ageAssurance/useBeginAgeAssurance.ts index 897b03bbdb..28a4591747 100644 --- a/src/ageAssurance/useBeginAgeAssurance.ts +++ b/src/ageAssurance/useBeginAgeAssurance.ts @@ -12,6 +12,7 @@ import {isNetworkError} from '#/lib/hooks/useCleanError' import {useAgent} from '#/state/session' import {usePatchAgeAssuranceServerState} from '#/ageAssurance' import {logger} from '#/ageAssurance/logger' +import {useAnalytics} from '#/analytics' import {BLUESKY_PROXY_DID} from '#/env' import {useGeolocation} from '#/geolocation' @@ -19,6 +20,7 @@ const IS_DEV_ENV = BLUESKY_PROXY_DID !== PUBLIC_APPVIEW_DID const APPVIEW = IS_DEV_ENV ? DEV_ENV_APPVIEW : PUBLIC_APPVIEW export function useBeginAgeAssurance() { + const ax = useAnalytics() const agent = useAgent() const geolocation = useGeolocation() const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState() @@ -48,15 +50,11 @@ export function useBeginAgeAssurance() { appView.sessionManager.session.accessJwt = token appView.sessionManager.session.refreshJwt = '' - logger.metric( - 'ageAssurance:api:begin', - { - platform: Platform.OS, - countryCode, - regionCode, - }, - {statsig: false}, - ) + ax.metric('ageAssurance:api:begin', { + platform: Platform.OS, + countryCode, + regionCode, + }) /* * 2s wait is good actually. Email sending takes a hot sec and this helps diff --git a/src/analytics/index.tsx b/src/analytics/index.tsx index 3753e335c1..ea538d1ca7 100644 --- a/src/analytics/index.tsx +++ b/src/analytics/index.tsx @@ -11,7 +11,7 @@ import { } from '#/analytics/features' import { getAndMigrateDeviceId, - getDeviceIdOrThrow, + getDeviceId, getInitialSessionId, useSessionId, } from '#/analytics/identifiers' @@ -24,6 +24,7 @@ import {device} from '#/storage' export * as utils from '#/analytics/utils' export const features = {init, refresh} +export {Features} from '#/analytics/features' export {type Metrics} from '#/analytics/metrics' type LoggerType = { @@ -38,7 +39,7 @@ type LoggerType = { useContext: (context: Exclude) => LoggerType Context: typeof Logger.Context } -type AnalyticsContextType = { +export type AnalyticsContextType = { metadata: Metadata logger: LoggerType metric: ( @@ -49,7 +50,7 @@ type AnalyticsContextType = { feature: (feature: Features) => boolean Features: typeof Features } -type AnalyticsBaseContextType = Omit< +export type AnalyticsBaseContextType = Omit< AnalyticsContextType, 'feature' | 'Features' > @@ -75,11 +76,14 @@ function createLogger( const Context = createContext({ logger: createLogger(Logger.Context.Default, {}), metric: (event, payload, metadata) => { + if (metadata && '__meta' in metadata) { + delete metadata.__meta + } metrics.track(event, payload, metadata) }, metadata: { base: { - deviceId: getDeviceIdOrThrow() ?? 'unknown', + deviceId: getDeviceId() ?? 'unknown', sessionId: getInitialSessionId(), platform: Platform.OS, appVersion: env.APP_VERSION, @@ -114,8 +118,7 @@ export function AnalyticsContext({ metadata?: MergeableMetadata }) { if (metadata) { - // @ts-ignore - if (metadata.__meta !== true) { + if (!('__meta' in metadata)) { throw new Error( 'Use the useMeta() helper when passing metadata to AnalyticsContext', ) diff --git a/src/analytics/metrics/client.ts b/src/analytics/metrics/client.ts index 42f50c38de..b47d3154d5 100644 --- a/src/analytics/metrics/client.ts +++ b/src/analytics/metrics/client.ts @@ -74,7 +74,7 @@ export class MetricsClient> { }) try { - const body = JSON.stringify(events) + const body = JSON.stringify({events}) if (env.IS_WEB && 'navigator' in globalThis && navigator.sendBeacon) { navigator.sendBeacon( TRACKING_ENDPOINT, diff --git a/src/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index 0d2119da92..0fa5279edf 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -148,6 +148,7 @@ export type Events = { feedUrl: string feedType: string index: number + reason?: string } 'feed:endReached': { feedUrl: string diff --git a/src/components/PostControls/BookmarkButton.tsx b/src/components/PostControls/BookmarkButton.tsx index ac391a1ae9..3b8e8db3d9 100644 --- a/src/components/PostControls/BookmarkButton.tsx +++ b/src/components/PostControls/BookmarkButton.tsx @@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react' import type React from 'react' import {useCleanError} from '#/lib/hooks/useCleanError' -import {logger} from '#/logger' import {type Shadow} from '#/state/cache/post-shadow' import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' @@ -15,6 +14,7 @@ import {useTheme} from '#/alf' import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import * as toast from '#/components/Toast' +import {useAnalytics} from '#/analytics' import {PostControlButton, PostControlButtonIcon} from './PostControlButton' export const BookmarkButton = memo(function BookmarkButton({ @@ -29,6 +29,7 @@ export const BookmarkButton = memo(function BookmarkButton({ hitSlop?: Insets }): React.ReactNode { const t = useTheme() + const ax = useAnalytics() const {_} = useLingui() const {mutateAsync: bookmark} = useBookmarkMutation() const cleanError = useCleanError() @@ -52,7 +53,7 @@ export const BookmarkButton = memo(function BookmarkButton({ post, }) - logger.metric('post:bookmark', { + ax.metric('post:bookmark', { uri: post.uri, authorDid: post.author.did, logContext, @@ -92,7 +93,7 @@ export const BookmarkButton = memo(function BookmarkButton({ uri: post.uri, }) - logger.metric('post:unbookmark', { + ax.metric('post:unbookmark', { uri: post.uri, authorDid: post.author.did, logContext, diff --git a/src/components/PostControls/DiscoverDebug.tsx b/src/components/PostControls/DiscoverDebug.tsx index 93d6126d99..5c18322c5e 100644 --- a/src/components/PostControls/DiscoverDebug.tsx +++ b/src/components/PostControls/DiscoverDebug.tsx @@ -3,11 +3,11 @@ import * as Clipboard from 'expo-clipboard' import {t} from '@lingui/macro' import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' -import {useGate} from '#/lib/statsig/statsig' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_INTERNAL} from '#/env' export function DiscoverDebug({ @@ -15,12 +15,12 @@ export function DiscoverDebug({ }: { feedContext: string | undefined }) { + const ax = useAnalytics() const {currentAccount} = useSession() - const gate = useGate() const isDiscoverDebugUser = IS_INTERNAL || DISCOVER_DEBUG_DIDS[currentAccount?.did || ''] || - gate('debug_show_feedcontext') + ax.feature(ax.Features.DebugFeedContext) const theme = useTheme() return ( diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index ce93e81ab1..1413678d2e 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -26,15 +26,17 @@ import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' -import {logEvent, useGate} from '#/lib/statsig/statsig' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {toShareUrl} from '#/lib/strings/url-helpers' import {logger} from '#/logger' import {type Shadow} from '#/state/cache/post-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useFeedFeedbackContext} from '#/state/feed-feedback' -import {useLanguagePrefs} from '#/state/preferences' -import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' +import { + useHiddenPosts, + useHiddenPostsApi, + useLanguagePrefs, +} from '#/state/preferences' import {usePinnedPostMutation} from '#/state/queries/pinned-post' import { usePostDeleteMutation, @@ -71,13 +73,17 @@ import { import {Eye_Stroke2_Corner0_Rounded as Eye} from '#/components/icons/Eye' import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' -import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' -import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' +import { + Mute_Stroke2_Corner0_Rounded as Mute, + Mute_Stroke2_Corner0_Rounded as MuteIcon, +} from '#/components/icons/Mute' import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Person' import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' -import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' -import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' +import { + SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute, + SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon, +} from '#/components/icons/Speaker' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' import {Loader} from '#/components/Loader' @@ -87,6 +93,7 @@ import { useReportDialogControl, } from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' +import {useAnalytics} from '#/analytics' import {IS_INTERNAL} from '#/env' import * as bsky from '#/types/bsky' @@ -116,6 +123,7 @@ let PostMenuItems = ({ }): React.ReactNode => { const {hasSession, currentAccount} = useSession() const {_} = useLingui() + const ax = useAnalytics() const langPrefs = useLanguagePrefs() const {mutateAsync: deletePostMutate} = usePostDeleteMutation() const {mutateAsync: pinPostMutate, isPending: isPinPending} = @@ -212,7 +220,7 @@ let PostMenuItems = ({ try { if (isThreadMuted) { unmuteThread() - logger.metric('post:unmute', { + ax.metric('post:unmute', { uri: postUri, authorDid: postAuthor.did, logContext, @@ -221,7 +229,7 @@ let PostMenuItems = ({ Toast.show(_(msg`You will now receive notifications for this thread`)) } else { muteThread() - logger.metric('post:mute', { + ax.metric('post:mute', { uri: postUri, authorDid: postAuthor.did, logContext, @@ -258,21 +266,17 @@ let PostMenuItems = ({ AppBskyFeedPost.isRecord, ) ) { - logger.metric( - 'translate', - { - sourceLanguages: post.record.langs ?? [], - targetLanguage: langPrefs.primaryLanguage, - textLength: post.record.text.length, - }, - {statsig: false}, - ) + ax.metric('translate', { + sourceLanguages: post.record.langs ?? [], + targetLanguage: langPrefs.primaryLanguage, + textLength: post.record.text.length, + }) } } const onHidePost = () => { hidePost({uri: postUri}) - logEvent('thread:click:hideReplyForMe', {}) + ax.metric('thread:click:hideReplyForMe', {}) } const hideInPWI = !!postAuthor.labels?.find( @@ -286,7 +290,7 @@ let PostMenuItems = ({ feedContext: postFeedContext, reqId: postReqId, }) - logger.metric('post:showMore', { + ax.metric('post:showMore', { uri: postUri, authorDid: postAuthor.did, logContext, @@ -304,7 +308,7 @@ let PostMenuItems = ({ feedContext: postFeedContext, reqId: postReqId, }) - logger.metric('post:showLess', { + ax.metric('post:showLess', { uri: postUri, authorDid: postAuthor.did, logContext, @@ -368,7 +372,7 @@ let PostMenuItems = ({ // Log metric only when hiding (not when showing) if (isHide) { - logEvent('thread:click:hideReplyForEveryone', {}) + ax.metric('thread:click:hideReplyForEveryone', {}) } Toast.show( @@ -405,7 +409,7 @@ let PostMenuItems = ({ } const onPressPin = () => { - logEvent(isPinned ? 'post:unpin' : 'post:pin', {}) + ax.metric(isPinned ? 'post:unpin' : 'post:pin', {}) pinPostMutate({ postUri, postCid, @@ -458,11 +462,10 @@ let PostMenuItems = ({ const onSignIn = () => requireSignIn(() => {}) - const gate = useGate() const isDiscoverDebugUser = IS_INTERNAL || DISCOVER_DEBUG_DIDS[currentAccount?.did || ''] || - gate('debug_show_feedcontext') + ax.feature(ax.Features.DebugFeedContext) return ( <> diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index e17a6adb2a..2648b485e1 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -8,7 +8,6 @@ import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' import {type NavigationProp} from '#/lib/routes/types' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' -import {logger} from '#/logger' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useListConvosQuery} from '#/state/queries/messages/list-conversations' @@ -20,9 +19,11 @@ import {useDialogContext} from '#/components/Dialog' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' import {VerificationCheck} from '#/components/verification/VerificationCheck' +import {useAnalytics} from '#/analytics' import type * as bsky from '#/types/bsky' export function RecentChats({postUri}: {postUri: string}) { + const ax = useAnalytics() const control = useDialogContext() const {currentAccount} = useSession() const {data} = useListConvosQuery({status: 'accepted'}) @@ -32,7 +33,7 @@ export function RecentChats({postUri}: {postUri: string}) { const onSelectChat = (convoId: string) => { control.close(() => { - logger.metric('share:press:recentDm', {}, {statsig: true}) + ax.metric('share:press:recentDm', {}) navigation.navigate('MessagesConversation', { conversation: convoId, embed: postUri, diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx index 8ba9ea3e25..fe2f0a9c77 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx @@ -9,7 +9,6 @@ import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {shareText, shareUrl} from '#/lib/sharing' import {toShareUrl} from '#/lib/strings/url-helpers' -import {logger} from '#/logger' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' @@ -23,6 +22,7 @@ import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/i import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane' import * as Menu from '#/components/Menu' import {useAgeAssurance} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' import {useDevMode} from '#/storage/hooks/dev-mode' import {RecentChats} from './RecentChats' @@ -32,6 +32,7 @@ let ShareMenuItems = ({ post, onShare: onShareProp, }: ShareMenuItemsProps): React.ReactNode => { + const ax = useAnalytics() const {hasSession} = useSession() const {_} = useLingui() const navigation = useNavigation() @@ -54,14 +55,14 @@ let ShareMenuItems = ({ }, [postAuthor]) const onSharePost = () => { - logger.metric('share:press:nativeShare', {}, {statsig: true}) + ax.metric('share:press:nativeShare', {}) const url = toShareUrl(href) shareUrl(url) onShareProp() } const onCopyLink = async () => { - logger.metric('share:press:copyLink', {}, {statsig: true}) + ax.metric('share:press:copyLink', {}) const url = toShareUrl(href) if (IS_IOS) { // iOS only @@ -100,7 +101,7 @@ let ShareMenuItems = ({ testID="postDropdownSendViaDMBtn" label={_(msg`Send via direct message`)} onPress={() => { - logger.metric('share:press:openDmSearch', {}, {statsig: true}) + ax.metric('share:press:openDmSearch', {}) sendViaChatControl.open() }}> diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx index 408035db90..c1da73ca82 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx @@ -8,7 +8,6 @@ import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {shareText, shareUrl} from '#/lib/sharing' import {toShareUrl} from '#/lib/strings/url-helpers' -import {logger} from '#/logger' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useSession} from '#/state/session' import {useBreakpoints} from '#/alf' @@ -21,6 +20,7 @@ import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/compon import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' import * as Menu from '#/components/Menu' import {useAgeAssurance} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' import {useDevMode} from '#/storage/hooks/dev-mode' import {type ShareMenuItemsProps} from './ShareMenuItems.types' @@ -31,6 +31,7 @@ let ShareMenuItems = ({ timestamp, onShare: onShareProp, }: ShareMenuItemsProps): React.ReactNode => { + const ax = useAnalytics() const {hasSession} = useSession() const {gtMobile} = useBreakpoints() const {_} = useLingui() @@ -56,14 +57,14 @@ let ShareMenuItems = ({ }, [postAuthor]) const onCopyLink = () => { - logger.metric('share:press:copyLink', {}, {statsig: true}) + ax.metric('share:press:copyLink', {}) const url = toShareUrl(href) shareUrl(url) onShareProp() } const onSelectChatToShareTo = (conversation: string) => { - logger.metric('share:press:dmSelected', {}, {statsig: true}) + ax.metric('share:press:dmSelected', {}) navigation.navigate('MessagesConversation', { conversation, embed: postUri, @@ -102,7 +103,7 @@ let ShareMenuItems = ({ testID="postDropdownSendViaDMBtn" label={_(msg`Send via direct message`)} onPress={() => { - logger.metric('share:press:openDmSearch', {}, {statsig: true}) + ax.metric('share:press:openDmSearch', {}) sendViaChatControl.open() }}> @@ -117,7 +118,7 @@ let ShareMenuItems = ({ testID="postDropdownEmbedBtn" label={_(msg`Embed post`)} onPress={() => { - logger.metric('share:press:embed', {}, {statsig: true}) + ax.metric('share:press:embed', {}) embedPostControl.open() }}> {_(msg`Embed post`)} diff --git a/src/components/PostControls/ShareMenu/index.tsx b/src/components/PostControls/ShareMenu/index.tsx index 0748f1a371..b53bd63c01 100644 --- a/src/components/PostControls/ShareMenu/index.tsx +++ b/src/components/PostControls/ShareMenu/index.tsx @@ -13,7 +13,6 @@ import {useLingui} from '@lingui/react' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' import {toShareUrl} from '#/lib/strings/url-helpers' -import {logger} from '#/logger' import {type Shadow} from '#/state/cache/post-shadow' import {useFeedFeedbackContext} from '#/state/feed-feedback' import {EventStopper} from '#/view/com/util/EventStopper' @@ -21,6 +20,7 @@ import {native} from '#/alf' import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight' import {useMenuControl} from '#/components/Menu' import * as Menu from '#/components/Menu' +import {useAnalytics} from '#/analytics' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {ShareMenuItems} from './ShareMenuItems' @@ -47,6 +47,7 @@ let ShareMenuButton = ({ hitSlop?: Insets logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' }): React.ReactNode => { + const ax = useAnalytics() const {_} = useLingui() const {feedDescriptor} = useFeedFeedbackContext() @@ -61,20 +62,17 @@ let ShareMenuButton = ({ // menuControl.open() fires but RN doesn't expose flushSync. setTimeout(menuControl.open) - logger.metric( - 'post:share', - { - uri: post.uri, - authorDid: post.author.did, - logContext, - feedDescriptor, - postContext: big ? 'thread' : 'feed', - }, - {statsig: true}, - ) + ax.metric('post:share', { + uri: post.uri, + authorDid: post.author.did, + logContext, + feedDescriptor, + postContext: big ? 'thread' : 'feed', + }) }, }), [ + ax, menuControl, setHasBeenOpen, big, @@ -86,7 +84,7 @@ let ShareMenuButton = ({ ) const onNativeLongPress = () => { - logger.metric('share:press:nativeShare', {}, {statsig: true}) + ax.metric('share:press:nativeShare', {}) const urip = new AtUri(post.uri) const href = makeProfileLink(post.author, 'post', urip.rkey) const url = toShareUrl(href) diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx index 45688c2617..4eb67879ab 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -13,7 +13,6 @@ import {CountWheel} from '#/lib/custom-animations/CountWheel' import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon' import {useHaptics} from '#/lib/haptics' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' -import {logger} from '#/logger' import {type Shadow} from '#/state/cache/types' import {useFeedFeedbackContext} from '#/state/feed-feedback' import { @@ -30,6 +29,7 @@ import {atoms as a, useBreakpoints} from '#/alf' import {Reply as Bubble} from '#/components/icons/Reply' import {useFormatPostStatCount} from '#/components/PostControls/util' import * as Skele from '#/components/Skeleton' +import {useAnalytics} from '#/analytics' import {BookmarkButton} from './BookmarkButton' import { PostControlButton, @@ -71,6 +71,7 @@ let PostControls = ({ viaRepost?: {uri: string; cid: string} variant?: 'compact' | 'normal' | 'large' }): React.ReactNode => { + const ax = useAnalytics() const {_} = useLingui() const {openComposer} = useOpenComposer() const {feedDescriptor} = useFeedFeedbackContext() @@ -175,7 +176,7 @@ let PostControls = ({ feedContext, reqId, }) - logger.metric('post:clickQuotePost', { + ax.metric('post:clickQuotePost', { uri: post.uri, authorDid: post.author.did, logContext, @@ -226,7 +227,7 @@ let PostControls = ({ !replyDisabled ? () => requireAuth(() => { - logger.metric('post:clickReply', { + ax.metric('post:clickReply', { uri: post.uri, authorDid: post.author.did, logContext, diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index fe4e29d98a..78ac868b28 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -10,8 +10,6 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' -import {logEvent} from '#/lib/statsig/statsig' -import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorSearch} from '#/state/queries/actor-search' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -36,6 +34,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {boostInterests, InterestTabs} from '#/components/InterestTabs' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' import type * as bsky from '#/types/bsky' import {ProgressGuideTask} from './Task' @@ -67,6 +66,7 @@ export function FollowDialog({ guide: Follow10ProgressGuide showArrow?: boolean }) { + const ax = useAnalytics() const {_} = useLingui() const control = Dialog.useDialogControl() const {gtPhone} = useBreakpoints() @@ -78,7 +78,7 @@ export function FollowDialog({ label={_(msg`Find people to follow`)} onPress={() => { control.open() - logEvent('progressGuide:followDialog:open', {}) + ax.metric('progressGuide:followDialog:open', {}) }} size={gtPhone ? 'small' : 'large'} color="primary"> @@ -118,6 +118,7 @@ let lastSearchText = '' function DialogInner({guide}: {guide?: Follow10ProgressGuide}) { const {_} = useLingui() + const ax = useAnalytics() const interestsDisplayNames = useInterestsDisplayNames() const {data: preferences} = usePreferencesQuery() const personalizedInterests = preferences?.interests?.tags @@ -271,17 +272,13 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) { const position = itemsRef.current.findIndex( i => i.type === 'profile' && i.profile.did === item.profile.did, ) - logger.metric( - 'suggestedUser:seen', - { - logContext: 'ProgressGuide', - recId: undefined, - position: position !== -1 ? position : 0, - suggestedDid: item.profile.did, - category: selectedInterestRef.current, - }, - {statsig: true}, - ) + ax.metric('suggestedUser:seen', { + logContext: 'ProgressGuide', + recId: undefined, + position: position !== -1 ? position : 0, + suggestedDid: item.profile.did, + category: selectedInterestRef.current, + }) } } } diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx index 4c9bcc510d..e0c894a769 100644 --- a/src/components/StarterPack/QrCodeDialog.tsx +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -19,6 +19,7 @@ import {FloppyDisk_Stroke2_Corner0_Rounded as FloppyDiskIcon} from '#/components import {Loader} from '#/components/Loader' import {QrCode} from '#/components/StarterPack/QrCode' import * as Toast from '#/components/Toast' +import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' import * as bsky from '#/types/bsky' @@ -32,6 +33,7 @@ export function QrCodeDialog({ control: DialogControlProps }) { const {_} = useLingui() + const ax = useAnalytics() const {gtMobile} = useBreakpoints() const [isSaveProcessing, setIsSaveProcessing] = useState(false) const [isCopyProcessing, setIsCopyProcessing] = useState(false) @@ -104,7 +106,7 @@ export function QrCodeDialog({ link.click() } - logger.metric('starterPack:share', { + ax.metric('starterPack:share', { starterPack: starterPack.uri, shareType: 'qrcode', qrShareType: 'save', @@ -129,7 +131,7 @@ export function QrCodeDialog({ navigator.clipboard.write([item]) }) - logger.metric('starterPack:share', { + ax.metric('starterPack:share', { starterPack: starterPack.uri, shareType: 'qrcode', qrShareType: 'copy', @@ -145,7 +147,7 @@ export function QrCodeDialog({ control.close(() => { Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then( () => { - logger.metric('starterPack:share', { + ax.metric('starterPack:share', { starterPack: starterPack.uri, shareType: 'qrcode', qrShareType: 'share', diff --git a/src/components/StarterPack/ShareDialog.tsx b/src/components/StarterPack/ShareDialog.tsx index 1dbbb07eef..eb9e98b3b1 100644 --- a/src/components/StarterPack/ShareDialog.tsx +++ b/src/components/StarterPack/ShareDialog.tsx @@ -7,7 +7,6 @@ import {useLingui} from '@lingui/react' import {useSaveImageToMediaLibrary} from '#/lib/media/save-image' import {shareUrl} from '#/lib/sharing' import {getStarterPackOgCard} from '#/lib/strings/starter-pack' -import {logger} from '#/logger' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {type DialogControlProps} from '#/components/Dialog' @@ -17,6 +16,7 @@ import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/ico import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' interface Props { @@ -46,6 +46,7 @@ function ShareDialogInner({ control, }: Props) { const {_} = useLingui() + const ax = useAnalytics() const t = useTheme() const {gtMobile} = useBreakpoints() @@ -54,7 +55,7 @@ function ShareDialogInner({ const onShareLink = async () => { if (!link) return shareUrl(link) - logger.metric('starterPack:share', { + ax.metric('starterPack:share', { starterPack: starterPack.uri, shareType: 'link', }) diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx index 089092636d..b5cd15a112 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -13,7 +13,6 @@ import {useLingui} from '@lingui/react' import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' -import {logger} from '#/logger' import {useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import { @@ -25,6 +24,7 @@ import {Button, ButtonText} from '#/components/Button' import * as Toggle from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import type * as bsky from '#/types/bsky' function WizardListCard({ @@ -130,6 +130,7 @@ export function WizardProfileCard({ profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts }) { + const ax = useAnalytics() const {currentAccount} = useSession() // Determine the "main" profile for this starter pack - either targetDid or current account @@ -151,10 +152,10 @@ export function WizardProfileCard({ if (profile.did === targetProfileDid) return if (!included) { - logger.metric('starterPack:addUser', {}) + ax.metric('starterPack:addUser', {}) dispatch({type: 'AddProfile', profile}) } else { - logger.metric('starterPack:removeUser', {}) + ax.metric('starterPack:removeUser', {}) dispatch({type: 'RemoveProfile', profileDid: profile.did}) } } diff --git a/src/components/WelcomeModal.tsx b/src/components/WelcomeModal.tsx index 1271a5f3ac..9b4ab08ebb 100644 --- a/src/components/WelcomeModal.tsx +++ b/src/components/WelcomeModal.tsx @@ -5,13 +5,13 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {FocusGuards, FocusScope} from 'radix-ui/internal' -import {logger} from '#/logger' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {Logo} from '#/view/icons/Logo' import {atoms as a, flatten, useBreakpoints, web} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' const welcomeModalBg = require('../../assets/images/welcome-modal-bg.jpg') @@ -25,6 +25,7 @@ interface WelcomeModalProps { export function WelcomeModal({control}: WelcomeModalProps) { const {_} = useLingui() + const ax = useAnalytics() const {requestSwitchToAccount} = useLoggedOutViewControls() const {gtMobile} = useBreakpoints() const [isExiting, setIsExiting] = useState(false) @@ -40,23 +41,24 @@ export function WelcomeModal({control}: WelcomeModalProps) { useEffect(() => { if (control.isOpen) { - logger.metric('welcomeModal:presented', {}) + ax.metric('welcomeModal:presented', {}) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [control.isOpen]) const onPressCreateAccount = () => { - logger.metric('welcomeModal:signupClicked', {}) + ax.metric('welcomeModal:signupClicked', {}) control.close() requestSwitchToAccount({requestedAccount: 'new'}) } const onPressExplore = () => { - logger.metric('welcomeModal:exploreClicked', {}) + ax.metric('welcomeModal:exploreClicked', {}) fadeOutAndClose() } const onPressSignIn = () => { - logger.metric('welcomeModal:signinClicked', {}) + ax.metric('welcomeModal:signinClicked', {}) control.close() requestSwitchToAccount({requestedAccount: 'existing'}) } @@ -222,7 +224,7 @@ export function WelcomeModal({control}: WelcomeModalProps) { ]} hoverStyle={[a.bg_transparent]} onPress={() => { - logger.metric('welcomeModal:dismissed', {}) + ax.metric('welcomeModal:dismissed', {}) fadeOutAndClose() }} color="secondary" diff --git a/src/components/WhoCanReply.tsx b/src/components/WhoCanReply.tsx index 292d041b70..8e93ee4fcc 100644 --- a/src/components/WhoCanReply.tsx +++ b/src/components/WhoCanReply.tsx @@ -17,7 +17,6 @@ import {useLingui} from '@lingui/react' import {HITSLOP_10} from '#/lib/constants' import {makeListLink, makeProfileLink} from '#/lib/routes/links' -import {logger} from '#/logger' import { type ThreadgateAllowUISetting, threadgateViewToAllowUISetting, @@ -36,6 +35,7 @@ import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Glo import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import * as bsky from '#/types/bsky' @@ -46,8 +46,9 @@ interface WhoCanReplyProps { } export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { - const {_} = useLingui() const t = useTheme() + const ax = useAnalytics() + const {_} = useLingui() const infoDialogControl = useDialogControl() const editDialogControl = useDialogControl() @@ -90,7 +91,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { Keyboard.dismiss() } if (isThreadAuthor) { - logger.metric('thread:click:editOwnThreadgate', {}) + ax.metric('thread:click:editOwnThreadgate', {}) // wait on prefetch if it manages to resolve in under 200ms // otherwise, proceed immediately and show the spinner -sfn @@ -101,7 +102,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { editDialogControl.open() }) } else { - logger.metric('thread:click:viewSomeoneElsesThreadgate', {}) + ax.metric('thread:click:viewSomeoneElsesThreadgate', {}) infoDialogControl.open() } diff --git a/src/components/activity-notifications/SubscribeProfileDialog.tsx b/src/components/activity-notifications/SubscribeProfileDialog.tsx index a8a5df39f2..cb0cae9fcc 100644 --- a/src/components/activity-notifications/SubscribeProfileDialog.tsx +++ b/src/components/activity-notifications/SubscribeProfileDialog.tsx @@ -17,13 +17,11 @@ import { import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {cleanError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' -import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions' import {useAgent} from '#/state/session' import * as Toast from '#/view/com/util/Toast' -import {platform, useTheme, web} from '#/alf' -import {atoms as a} from '#/alf' +import {atoms as a, platform, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import { Button, @@ -36,6 +34,7 @@ import * as Toggle from '#/components/forms/Toggle' import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' import type * as bsky from '#/types/bsky' @@ -71,6 +70,7 @@ function DialogInner({ moderationOpts: ModerationOpts includeProfile?: boolean }) { + const ax = useAnalytics() const {_} = useLingui() const t = useTheme() const agent = useAgent() @@ -133,7 +133,7 @@ function DialogInner({ }) if (!activitySubscription.post && !activitySubscription.reply) { - logger.metric('activitySubscription:disable', {}) + ax.metric('activitySubscription:disable', {}) Toast.show( _( msg`You will no longer receive notifications for ${sanitizeHandle(profile.handle, '@')}`, @@ -160,7 +160,7 @@ function DialogInner({ }, ) } else { - logger.metric('activitySubscription:enable', { + ax.metric('activitySubscription:enable', { setting: activitySubscription.reply ? 'posts_and_replies' : 'posts', }) if (!initialState.post && !initialState.reply) { @@ -177,7 +177,7 @@ function DialogInner({ }) }, onError: err => { - logger.error('Could not save activity subscription', {message: err}) + ax.logger.error('Could not save activity subscription', {message: err}) }, }) diff --git a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx index 41659d3edb..7d7b5ade06 100644 --- a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx +++ b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx @@ -20,8 +20,9 @@ import {Divider} from '#/components/Divider' import {createStaticClick, InlineLinkText} from '#/components/Link' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' -import {logger, useAgeAssurance} from '#/ageAssurance' +import {useAgeAssurance} from '#/ageAssurance' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' +import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import {useDeviceGeolocationApi} from '#/geolocation' @@ -41,6 +42,7 @@ export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { function Inner({style}: ViewStyleProp & {}) { const t = useTheme() const {_, i18n} = useLingui() + const ax = useAnalytics() const control = useDialogControl() const appealControl = Dialog.useDialogControl() const locationControl = Dialog.useDialogControl() @@ -138,7 +140,7 @@ function Inner({style}: ViewStyleProp & {}) { label={_(msg`Contact our moderation team`)} {...createStaticClick(() => { appealControl.open() - logger.metric('ageAssurance:appealDialogOpen', {}) + ax.metric('ageAssurance:appealDialogOpen', {}) })}> contact our moderation team {' '} @@ -167,7 +169,7 @@ function Inner({style}: ViewStyleProp & {}) { color={hasInitiated ? 'secondary' : 'primary'} onPress={() => { control.open() - logger.metric('ageAssurance:initDialogOpen', { + ax.metric('ageAssurance:initDialogOpen', { hasInitiatedPreviously: hasInitiated, }) }}> diff --git a/src/components/ageAssurance/AgeAssuranceAdmonition.tsx b/src/components/ageAssurance/AgeAssuranceAdmonition.tsx index 1ef29dfbbb..426924b637 100644 --- a/src/components/ageAssurance/AgeAssuranceAdmonition.tsx +++ b/src/components/ageAssurance/AgeAssuranceAdmonition.tsx @@ -10,7 +10,7 @@ import {ShieldCheck_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/ import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' -import {logger} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' export function AgeAssuranceAdmonition({ children, @@ -40,6 +40,7 @@ function Inner({ }) { const t = useTheme() const {_} = useLingui() + const ax = useAnalytics() return ( <> @@ -92,7 +93,7 @@ function Inner({ to={'/settings/account'} style={[a.text_sm, a.leading_snug, a.font_semi_bold]} onPress={() => { - logger.metric('ageAssurance:navigateToSettings', {}) + ax.metric('ageAssurance:navigateToSettings', {}) }}> account settings. diff --git a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx index d8330a94c3..c7f4c170e1 100644 --- a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx @@ -15,6 +15,7 @@ import * as Dialog from '#/components/Dialog' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {logger} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' export function AgeAssuranceAppealDialog({ control, @@ -37,6 +38,7 @@ export function AgeAssuranceAppealDialog({ function Inner({control}: {control: Dialog.DialogControlProps}) { const {_} = useLingui() + const ax = useAnalytics() const {currentAccount} = useSession() const {gtPhone} = useBreakpoints() const agent = useAgent() @@ -46,7 +48,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) { const {mutate, isPending} = useMutation({ mutationFn: async () => { - logger.metric('ageAssurance:appealDialogSubmit', {}) + ax.metric('ageAssurance:appealDialogSubmit', {}) await agent.createModerationReport( { diff --git a/src/components/ageAssurance/AgeAssuranceDismissibleFeedBanner.tsx b/src/components/ageAssurance/AgeAssuranceDismissibleFeedBanner.tsx index aa1d527f62..b32a2457b3 100644 --- a/src/components/ageAssurance/AgeAssuranceDismissibleFeedBanner.tsx +++ b/src/components/ageAssurance/AgeAssuranceDismissibleFeedBanner.tsx @@ -12,7 +12,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {Link} from '#/components/Link' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' -import {logger} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' export function useInternalState() { const aa = useAgeAssurance() @@ -42,6 +42,7 @@ export function useInternalState() { export function AgeAssuranceDismissibleFeedBanner() { const t = useTheme() + const ax = useAnalytics() const {_} = useLingui() const {visible, close} = useInternalState() const copy = useAgeAssuranceCopy() @@ -66,7 +67,7 @@ export function AgeAssuranceDismissibleFeedBanner() { to="/settings/account" onPress={() => { close() - logger.metric('ageAssurance:navigateToSettings', {}) + ax.metric('ageAssurance:navigateToSettings', {}) }} style={[a.w_full, a.justify_between, a.align_center, a.gap_md]}> { close() - logger.metric('ageAssurance:dismissFeedBanner', {}) + ax.metric('ageAssurance:dismissFeedBanner', {}) }} style={[ a.absolute, diff --git a/src/components/ageAssurance/AgeAssuranceDismissibleNotice.tsx b/src/components/ageAssurance/AgeAssuranceDismissibleNotice.tsx index 14c7ed300f..fcfdf18017 100644 --- a/src/components/ageAssurance/AgeAssuranceDismissibleNotice.tsx +++ b/src/components/ageAssurance/AgeAssuranceDismissibleNotice.tsx @@ -10,10 +10,11 @@ import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy import {Button, ButtonIcon} from '#/components/Button' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {useAgeAssurance} from '#/ageAssurance' -import {logger} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' export function AgeAssuranceDismissibleNotice({style}: ViewStyleProp & {}) { const {_} = useLingui() + const ax = useAnalytics() const aa = useAgeAssurance() const {nux} = useNux(Nux.AgeAssuranceDismissibleNotice) const copy = useAgeAssuranceCopy() @@ -45,7 +46,7 @@ export function AgeAssuranceDismissibleNotice({style}: ViewStyleProp & {}) { completed: true, data: undefined, }) - logger.metric('ageAssurance:dismissSettingsNotice', {}) + ax.metric('ageAssurance:dismissSettingsNotice', {}) }} style={[ a.absolute, diff --git a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx index 32f6b0044a..8414b5fbd8 100644 --- a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx @@ -19,8 +19,7 @@ import {useSession} from '#/state/session' import {atoms as a, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' -import {urls} from '#/components/ageAssurance/const' -import {KWS_SUPPORTED_LANGS} from '#/components/ageAssurance/const' +import {KWS_SUPPORTED_LANGS, urls} from '#/components/ageAssurance/const' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Divider} from '#/components/Divider' @@ -30,9 +29,9 @@ import {LanguageSelect} from '#/components/LanguageSelect' import {SimpleInlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' -import {logger} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance' import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance' +import {useAnalytics} from '#/analytics' export {useDialogControl} from '#/components/Dialog/context' @@ -64,6 +63,7 @@ export function AgeAssuranceInitDialog({ function Inner() { const {_} = useLingui() + const ax = useAnalytics() const {currentAccount} = useSession() const langPrefs = useLanguagePrefs() const cleanError = useCleanError() @@ -116,7 +116,7 @@ function Inner() { const onSubmit = async () => { setLanguageError(false) - logger.metric('ageAssurance:initDialogSubmit', {}) + ax.metric('ageAssurance:initDialogSubmit', {}) try { const {status} = runEmailValidation() @@ -143,7 +143,7 @@ function Inner() { error = _( msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`, ) - logger.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'}) + ax.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'}) } else if (e.error === 'DidTooLong') { error = ( <> @@ -159,14 +159,14 @@ function Inner() { ) - logger.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) + ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) } else { - logger.metric('ageAssurance:initDialogError', {code: 'other'}) + ax.metric('ageAssurance:initDialogError', {code: 'other'}) } } else { const {clean, raw} = cleanError(e) error = clean || raw || error - logger.metric('ageAssurance:initDialogError', {code: 'other'}) + ax.metric('ageAssurance:initDialogError', {code: 'other'}) } setError(error) diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx index 0cbf6bed04..fc8dfeef4a 100644 --- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -16,7 +16,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icon import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {refetchAgeAssuranceServerState} from '#/ageAssurance' -import {logger} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' export type AgeAssuranceRedirectDialogState = { @@ -81,6 +81,7 @@ export function AgeAssuranceRedirectDialog() { export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { const t = useTheme() + const ax = useAnalytics() const {_} = useLingui() const agent = useAgent() const polling = useRef(false) @@ -94,7 +95,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { polling.current = true - logger.metric('ageAssurance:redirectDialogOpen', {}) + ax.metric('ageAssurance:redirectDialogOpen', {}) wait( 3e3, @@ -125,18 +126,18 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { setSuccess(true) - logger.metric('ageAssurance:redirectDialogSuccess', {}) + ax.metric('ageAssurance:redirectDialogSuccess', {}) }) .catch(() => { if (unmounted.current) return setError(true) - logger.metric('ageAssurance:redirectDialogFail', {}) + ax.metric('ageAssurance:redirectDialogFail', {}) }) return () => { unmounted.current = true } - }, [agent, control]) + }, [ax, agent, control]) if (success) { return ( diff --git a/src/components/ageAssurance/AgeRestrictedScreen.tsx b/src/components/ageAssurance/AgeRestrictedScreen.tsx index 9f9c4c4d5b..8c77e8e43d 100644 --- a/src/components/ageAssurance/AgeRestrictedScreen.tsx +++ b/src/components/ageAssurance/AgeRestrictedScreen.tsx @@ -13,7 +13,7 @@ import * as Layout from '#/components/Layout' import {Link} from '#/components/Link' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' -import {logger} from '#/ageAssurance' +import {useAnalytics} from '#/analytics' export function AgeRestrictedScreen({ children, @@ -27,6 +27,7 @@ export function AgeRestrictedScreen({ rightHeaderSlot?: React.ReactNode }) { const {_} = useLingui() + const ax = useAnalytics() const copy = useAgeAssuranceCopy() const aa = useAgeAssurance() @@ -74,7 +75,7 @@ export function AgeRestrictedScreen({ variant="solid" color="primary" onPress={() => { - logger.metric('ageAssurance:navigateToSettings', {}) + ax.metric('ageAssurance:navigateToSettings', {}) }}> Go to account settings diff --git a/src/components/contacts/FindContactsBannerNUX.tsx b/src/components/contacts/FindContactsBannerNUX.tsx index 768fee519b..577474a3a4 100644 --- a/src/components/contacts/FindContactsBannerNUX.tsx +++ b/src/components/contacts/FindContactsBannerNUX.tsx @@ -6,12 +6,12 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10} from '#/lib/constants' -import {logger} from '#/logger' import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' import {Link} from '../Link' import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist' @@ -19,6 +19,7 @@ import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allow export function FindContactsBannerNUX() { const t = useTheme() const {_} = useLingui() + const ax = useAnalytics() const {visible, close} = useInternalState() if (!visible) return null @@ -30,7 +31,7 @@ export function FindContactsBannerNUX() { to={{screen: 'FindContactsFlow'}} label={_(msg`Import contacts to find your friends`)} onPress={() => { - logger.metric('contacts:nux:bannerPressed', {}) + ax.metric('contacts:nux:bannerPressed', {}) }} style={[ a.w_full, @@ -84,6 +85,7 @@ export function FindContactsBannerNUX() { ) } function useInternalState() { + const ax = useAnalytics() const {nux} = useNux(Nux.FindContactsDismissibleBanner) const {mutate: save, variables} = useSaveNux() const hidden = !!variables @@ -103,7 +105,7 @@ function useInternalState() { completed: true, data: undefined, }) - logger.metric('contacts:nux:bannerDismissed', {}) + ax.metric('contacts:nux:bannerDismissed', {}) } return {visible, close} diff --git a/src/components/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx index 5df967a8a5..909be81abc 100644 --- a/src/components/contacts/screens/GetContacts.tsx +++ b/src/components/contacts/screens/GetContacts.tsx @@ -28,6 +28,7 @@ import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import { contactsWithPhoneNumbersOnly, filterMatchedNumbers, @@ -51,6 +52,7 @@ export function GetContacts({ context: 'Onboarding' | 'Standalone' }) { const {_} = useLingui() + const ax = useAnalytics() const agent = useAgent() const insets = useSafeAreaInsets() const gutters = useGutters([0, 'wide']) @@ -100,16 +102,16 @@ export function GetContacts({ }, onSuccess: (result, contacts) => { if (context === 'Onboarding') { - logger.metric('onboarding:contacts:contactsShared', {}) + ax.metric('onboarding:contacts:contactsShared', {}) } if (result.matches.length > 0) { - logger.metric('contacts:import:success', { + ax.metric('contacts:import:success', { contactCount: contacts.length, matchCount: result.matches.length, entryPoint: context, }) } else { - logger.metric('contacts:import:failure', { + ax.metric('contacts:import:failure', { reason: 'noValidNumbers', entryPoint: context, }) @@ -134,7 +136,7 @@ export function GetContacts({ }) }, onError: err => { - logger.metric('contacts:import:failure', { + ax.metric('contacts:import:failure', { reason: isNetworkError(err) ? 'networkError' : 'unknown', entryPoint: context, }) @@ -180,7 +182,7 @@ export function GetContacts({ permissions = await Contacts.requestPermissionsAsync() } - logger.metric('contacts:permission:request', { + ax.metric('contacts:permission:request', { status: permissions.granted ? 'granted' : 'denied', accessLevelIOS: ios(permissions.accessPrivileges), }) diff --git a/src/components/contacts/screens/PhoneInput.tsx b/src/components/contacts/screens/PhoneInput.tsx index 25bffaf144..8eec5742e8 100644 --- a/src/components/contacts/screens/PhoneInput.tsx +++ b/src/components/contacts/screens/PhoneInput.tsx @@ -31,6 +31,7 @@ import * as Layout from '#/components/Layout' import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {useGeolocation} from '#/geolocation' import {isFindContactsFeatureEnabled} from '../country-allowlist' import { @@ -52,6 +53,7 @@ export function PhoneInput({ onSkip: () => void }) { const {_} = useLingui() + const ax = useAnalytics() const t = useTheme() const agent = useAgent() const location = useGeolocation() @@ -85,7 +87,7 @@ export function PhoneInput({ payload: {phoneCountryCode, phoneNumber}, }) - logger.metric('contacts:phone:phoneEntered', {entryPoint: context}) + ax.metric('contacts:phone:phoneEntered', {entryPoint: context}) }, onMutate: () => { Keyboard.dismiss() diff --git a/src/components/contacts/screens/VerifyNumber.tsx b/src/components/contacts/screens/VerifyNumber.tsx index ec676ad724..c316c66af7 100644 --- a/src/components/contacts/screens/VerifyNumber.tsx +++ b/src/components/contacts/screens/VerifyNumber.tsx @@ -23,6 +23,7 @@ import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import {OTPInput} from '../components/OTPInput' import {constructFullPhoneNumber, prettyPhoneNumber} from '../phone-number' import {type Action, type State, useOnPressBackButton} from '../state' @@ -40,6 +41,7 @@ export function VerifyNumber({ }) { const t = useTheme() const {_} = useLingui() + const ax = useAnalytics() const agent = useAgent() const gutters = useGutters([0, 'wide']) @@ -83,7 +85,7 @@ export function VerifyNumber({ }) }, 1000) - logger.metric('contacts:phone:phoneVerified', {entryPoint: context}) + ax.metric('contacts:phone:phoneVerified', {entryPoint: context}) }, onMutate: () => setError(null), onError: err => { diff --git a/src/components/contacts/screens/ViewMatches.tsx b/src/components/contacts/screens/ViewMatches.tsx index d92cb88b1e..b9aec3ed95 100644 --- a/src/components/contacts/screens/ViewMatches.tsx +++ b/src/components/contacts/screens/ViewMatches.tsx @@ -39,6 +39,7 @@ import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {useAnalytics} from '#/analytics' import type * as bsky from '#/types/bsky' import {InviteInfo} from '../components/InviteInfo' import {type Action, type Contact, type Match, type State} from '../state' @@ -83,6 +84,7 @@ export function ViewMatches({ }) { const t = useTheme() const {_} = useLingui() + const ax = useAnalytics() const gutter = useGutters([0, 'wide']) const moderationOpts = useModerationOpts() const queryClient = useQueryClient() @@ -109,9 +111,9 @@ export function ViewMatches({ const cumulativeFollowCount = useRef(0) const onFollow = useCallback(() => { - logger.metric('contacts:matches:follow', {entryPoint: context}) + ax.metric('contacts:matches:follow', {entryPoint: context}) cumulativeFollowCount.current += 1 - }, [context]) + }, [ax, context]) const {mutate: followAll, isPending: isFollowingAll} = useMutation({ mutationFn: async () => { @@ -132,7 +134,7 @@ export function ViewMatches({ return followableDids }, onMutate: () => - logger.metric('contacts:matches:followAll', { + ax.metric('contacts:matches:followAll', { followCount: followableDids.length, entryPoint: context, }), @@ -218,7 +220,7 @@ export function ViewMatches({ await agent.app.bsky.contact.dismissMatch({subject: did}) }, onMutate: did => { - logger.metric('contacts:matches:dismiss', {entryPoint: context}) + ax.metric('contacts:matches:dismiss', {entryPoint: context}) dispatch({type: 'DISMISS_MATCH', payload: {did}}) }, onSuccess: (_res, did) => { @@ -392,7 +394,7 @@ export function ViewMatches({ label={context === 'Onboarding' ? _(msg`Next`) : _(msg`Done`)} onPress={() => { if (context === 'Onboarding') { - logger.metric('onboarding:contacts:nextPressed', { + ax.metric('onboarding:contacts:nextPressed', { matchCount: allMatches.length, followCount: cumulativeFollowCount.current, dismissedMatchCount: state.dismissedMatches.length, @@ -516,6 +518,7 @@ function ContactItem({ const gutter = useGutters([0, 'wide']) const t = useTheme() const {_} = useLingui() + const ax = useAnalytics() const {currentAccount} = useSession() const name = contact.name ?? contact.firstName ?? contact.lastName @@ -564,7 +567,7 @@ function ContactItem({ color="secondary" size="small" onPress={async () => { - logger.metric('contacts:matches:invite', { + ax.metric('contacts:matches:invite', { entryPoint: context, }) try { diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index 259395a8c9..23fd2f38e6 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -11,7 +11,6 @@ import {Image} from 'expo-image' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {logEvent} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import { type Gif, @@ -30,6 +29,7 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' export function GifSelectDialog({ @@ -280,14 +280,15 @@ export function GifPreview({ gif: Gif onSelectGif: (gif: Gif) => void }) { + const ax = useAnalytics() const {gtTablet} = useBreakpoints() const {_} = useLingui() const t = useTheme() const onPress = useCallback(() => { - logEvent('composer:gif:select', {}) + ax.metric('composer:gif:select', {}) onSelectGif(gif) - }, [onSelectGif, gif]) + }, [ax, onSelectGif, gif]) return (