Migrate logger.metric to useAnalytics

This commit is contained in:
Eric Bailey
2026-01-21 16:49:40 -06:00
parent 1c8081e81e
commit bf096977fe
86 changed files with 541 additions and 570 deletions
+9 -11
View File
@@ -9,7 +9,6 @@ import {
useCreateSupportLink, useCreateSupportLink,
} from '#/lib/hooks/useCreateSupportLink' } from '#/lib/hooks/useCreateSupportLink'
import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {logger} from '#/logger'
import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' import {useIsBirthdateUpdateAllowed} from '#/state/birthdate'
import {useSessionApi} from '#/state/session' import {useSessionApi} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
@@ -36,8 +35,8 @@ import {
isLegacyBirthdateBug, isLegacyBirthdateBug,
useAgeAssuranceRegionConfig, useAgeAssuranceRegionConfig,
} from '#/ageAssurance/util' } from '#/ageAssurance/util'
import {IS_WEB} from '#/env' import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {useDeviceGeolocationApi} from '#/geolocation' import {useDeviceGeolocationApi} from '#/geolocation'
const textStyles = [a.text_md, a.leading_snug] const textStyles = [a.text_md, a.leading_snug]
@@ -45,6 +44,7 @@ const textStyles = [a.text_md, a.leading_snug]
export function NoAccessScreen() { export function NoAccessScreen() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {gtPhone} = useBreakpoints() const {gtPhone} = useBreakpoints()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const birthdateControl = useDialogControl() const birthdateControl = useDialogControl()
@@ -63,8 +63,8 @@ export function NoAccessScreen() {
useEffect(() => { useEffect(() => {
// just counting overall hits here // just counting overall hits here
logger.metric(`blockedGeoOverlay:shown`, {}) ax.metric(`blockedGeoOverlay:shown`, {})
logger.metric(`ageAssurance:noAccessScreen:shown`, { ax.metric(`ageAssurance:noAccessScreen:shown`, {
accountCreatedAt: data?.accountCreatedAt || 'unknown', accountCreatedAt: data?.accountCreatedAt || 'unknown',
isAARegion, isAARegion,
hasDeclaredAge, hasDeclaredAge,
@@ -103,10 +103,7 @@ export function NoAccessScreen() {
label={_(msg`Click here to update your birthdate`)} label={_(msg`Click here to update your birthdate`)}
style={[textStyles]} style={[textStyles]}
{...createStaticClick(() => { {...createStaticClick(() => {
logger.metric( ax.metric('ageAssurance:noAccessScreen:openBirthdateDialog', {})
'ageAssurance:noAccessScreen:openBirthdateDialog',
{},
)
birthdateControl.open() birthdateControl.open()
})}> })}>
clicking here clicking here
@@ -272,6 +269,7 @@ export function NoAccessScreen() {
function AccessSection() { function AccessSection() {
const t = useTheme() const t = useTheme()
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const ax = useAnalytics()
const control = useDialogControl() const control = useDialogControl()
const appealControl = Dialog.useDialogControl() const appealControl = Dialog.useDialogControl()
const locationControl = Dialog.useDialogControl() const locationControl = Dialog.useDialogControl()
@@ -305,7 +303,7 @@ function AccessSection() {
label={_(msg`Contact our moderation team`)} label={_(msg`Contact our moderation team`)}
{...createStaticClick(() => { {...createStaticClick(() => {
appealControl.open() appealControl.open()
logger.metric('ageAssurance:appealDialogOpen', {}) ax.metric('ageAssurance:appealDialogOpen', {})
})}> })}>
contact our moderation team contact our moderation team
</SimpleInlineLinkText>{' '} </SimpleInlineLinkText>{' '}
@@ -321,7 +319,7 @@ function AccessSection() {
color={hasInitiated ? 'secondary' : 'primary'} color={hasInitiated ? 'secondary' : 'primary'}
onPress={() => { onPress={() => {
control.open() control.open()
logger.metric('ageAssurance:initDialogOpen', { ax.metric('ageAssurance:initDialogOpen', {
hasInitiatedPreviously: hasInitiated, hasInitiatedPreviously: hasInitiated,
}) })
}}> }}>
@@ -25,9 +25,8 @@ import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icon
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {refetchAgeAssuranceServerState} from '#/ageAssurance' import {refetchAgeAssuranceServerState} from '#/ageAssurance'
import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_IOS, IS_WEB} from '#/env'
import {IS_IOS} from '#/env'
export type RedirectOverlayState = { export type RedirectOverlayState = {
result: 'success' | 'unknown' result: 'success' | 'unknown'
@@ -174,6 +173,7 @@ export function RedirectOverlay() {
function Inner() { function Inner() {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const agent = useAgent() const agent = useAgent()
const polling = useRef(false) const polling = useRef(false)
@@ -187,7 +187,7 @@ function Inner() {
polling.current = true polling.current = true
logger.metric('ageAssurance:redirectDialogOpen', {}) ax.metric('ageAssurance:redirectDialogOpen', {})
wait( wait(
3e3, 3e3,
@@ -218,18 +218,18 @@ function Inner() {
setSuccess(true) setSuccess(true)
logger.metric('ageAssurance:redirectDialogSuccess', {}) ax.metric('ageAssurance:redirectDialogSuccess', {})
}) })
.catch(() => { .catch(() => {
if (unmounted.current) return if (unmounted.current) return
setError(true) setError(true)
logger.metric('ageAssurance:redirectDialogFail', {}) ax.metric('ageAssurance:redirectDialogFail', {})
}) })
return () => { return () => {
unmounted.current = true unmounted.current = true
} }
}, [agent]) }, [ax, agent])
if (success) { if (success) {
return ( return (
+7 -9
View File
@@ -12,6 +12,7 @@ import {isNetworkError} from '#/lib/hooks/useCleanError'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {usePatchAgeAssuranceServerState} from '#/ageAssurance' import {usePatchAgeAssuranceServerState} from '#/ageAssurance'
import {logger} from '#/ageAssurance/logger' import {logger} from '#/ageAssurance/logger'
import {useAnalytics} from '#/analytics'
import {BLUESKY_PROXY_DID} from '#/env' import {BLUESKY_PROXY_DID} from '#/env'
import {useGeolocation} from '#/geolocation' 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 const APPVIEW = IS_DEV_ENV ? DEV_ENV_APPVIEW : PUBLIC_APPVIEW
export function useBeginAgeAssurance() { export function useBeginAgeAssurance() {
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const geolocation = useGeolocation() const geolocation = useGeolocation()
const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState() const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState()
@@ -48,15 +50,11 @@ export function useBeginAgeAssurance() {
appView.sessionManager.session.accessJwt = token appView.sessionManager.session.accessJwt = token
appView.sessionManager.session.refreshJwt = '' appView.sessionManager.session.refreshJwt = ''
logger.metric( ax.metric('ageAssurance:api:begin', {
'ageAssurance:api:begin', platform: Platform.OS,
{ countryCode,
platform: Platform.OS, regionCode,
countryCode, })
regionCode,
},
{statsig: false},
)
/* /*
* 2s wait is good actually. Email sending takes a hot sec and this helps * 2s wait is good actually. Email sending takes a hot sec and this helps
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import type React from 'react' import type React from 'react'
import {useCleanError} from '#/lib/hooks/useCleanError' import {useCleanError} from '#/lib/hooks/useCleanError'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/post-shadow' import {type Shadow} from '#/state/cache/post-shadow'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
@@ -15,6 +14,7 @@ import {useTheme} from '#/alf'
import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark' import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
import * as toast from '#/components/Toast' import * as toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {PostControlButton, PostControlButtonIcon} from './PostControlButton' import {PostControlButton, PostControlButtonIcon} from './PostControlButton'
export const BookmarkButton = memo(function BookmarkButton({ export const BookmarkButton = memo(function BookmarkButton({
@@ -29,6 +29,7 @@ export const BookmarkButton = memo(function BookmarkButton({
hitSlop?: Insets hitSlop?: Insets
}): React.ReactNode { }): React.ReactNode {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {mutateAsync: bookmark} = useBookmarkMutation() const {mutateAsync: bookmark} = useBookmarkMutation()
const cleanError = useCleanError() const cleanError = useCleanError()
@@ -52,7 +53,7 @@ export const BookmarkButton = memo(function BookmarkButton({
post, post,
}) })
logger.metric('post:bookmark', { ax.metric('post:bookmark', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext, logContext,
@@ -92,7 +93,7 @@ export const BookmarkButton = memo(function BookmarkButton({
uri: post.uri, uri: post.uri,
}) })
logger.metric('post:unbookmark', { ax.metric('post:unbookmark', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext, logContext,
@@ -8,7 +8,6 @@ import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useListConvosQuery} from '#/state/queries/messages/list-conversations' import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
@@ -20,9 +19,11 @@ import {useDialogContext} from '#/components/Dialog'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification' import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck' import {VerificationCheck} from '#/components/verification/VerificationCheck'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function RecentChats({postUri}: {postUri: string}) { export function RecentChats({postUri}: {postUri: string}) {
const ax = useAnalytics()
const control = useDialogContext() const control = useDialogContext()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {data} = useListConvosQuery({status: 'accepted'}) const {data} = useListConvosQuery({status: 'accepted'})
@@ -32,7 +33,7 @@ export function RecentChats({postUri}: {postUri: string}) {
const onSelectChat = (convoId: string) => { const onSelectChat = (convoId: string) => {
control.close(() => { control.close(() => {
logger.metric('share:press:recentDm', {}, {statsig: true}) ax.metric('share:press:recentDm', {})
navigation.navigate('MessagesConversation', { navigation.navigate('MessagesConversation', {
conversation: convoId, conversation: convoId,
embed: postUri, embed: postUri,
@@ -9,7 +9,6 @@ import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {shareText, shareUrl} from '#/lib/sharing' import {shareText, shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast' 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 {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {useDevMode} from '#/storage/hooks/dev-mode' import {useDevMode} from '#/storage/hooks/dev-mode'
import {RecentChats} from './RecentChats' import {RecentChats} from './RecentChats'
@@ -32,6 +32,7 @@ let ShareMenuItems = ({
post, post,
onShare: onShareProp, onShare: onShareProp,
}: ShareMenuItemsProps): React.ReactNode => { }: ShareMenuItemsProps): React.ReactNode => {
const ax = useAnalytics()
const {hasSession} = useSession() const {hasSession} = useSession()
const {_} = useLingui() const {_} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@@ -54,14 +55,14 @@ let ShareMenuItems = ({
}, [postAuthor]) }, [postAuthor])
const onSharePost = () => { const onSharePost = () => {
logger.metric('share:press:nativeShare', {}, {statsig: true}) ax.metric('share:press:nativeShare', {})
const url = toShareUrl(href) const url = toShareUrl(href)
shareUrl(url) shareUrl(url)
onShareProp() onShareProp()
} }
const onCopyLink = async () => { const onCopyLink = async () => {
logger.metric('share:press:copyLink', {}, {statsig: true}) ax.metric('share:press:copyLink', {})
const url = toShareUrl(href) const url = toShareUrl(href)
if (IS_IOS) { if (IS_IOS) {
// iOS only // iOS only
@@ -100,7 +101,7 @@ let ShareMenuItems = ({
testID="postDropdownSendViaDMBtn" testID="postDropdownSendViaDMBtn"
label={_(msg`Send via direct message`)} label={_(msg`Send via direct message`)}
onPress={() => { onPress={() => {
logger.metric('share:press:openDmSearch', {}, {statsig: true}) ax.metric('share:press:openDmSearch', {})
sendViaChatControl.open() sendViaChatControl.open()
}}> }}>
<Menu.ItemText> <Menu.ItemText>
@@ -8,7 +8,6 @@ import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {shareText, shareUrl} from '#/lib/sharing' import {shareText, shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useBreakpoints} from '#/alf' 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 {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {useDevMode} from '#/storage/hooks/dev-mode' import {useDevMode} from '#/storage/hooks/dev-mode'
import {type ShareMenuItemsProps} from './ShareMenuItems.types' import {type ShareMenuItemsProps} from './ShareMenuItems.types'
@@ -31,6 +31,7 @@ let ShareMenuItems = ({
timestamp, timestamp,
onShare: onShareProp, onShare: onShareProp,
}: ShareMenuItemsProps): React.ReactNode => { }: ShareMenuItemsProps): React.ReactNode => {
const ax = useAnalytics()
const {hasSession} = useSession() const {hasSession} = useSession()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const {_} = useLingui() const {_} = useLingui()
@@ -56,14 +57,14 @@ let ShareMenuItems = ({
}, [postAuthor]) }, [postAuthor])
const onCopyLink = () => { const onCopyLink = () => {
logger.metric('share:press:copyLink', {}, {statsig: true}) ax.metric('share:press:copyLink', {})
const url = toShareUrl(href) const url = toShareUrl(href)
shareUrl(url) shareUrl(url)
onShareProp() onShareProp()
} }
const onSelectChatToShareTo = (conversation: string) => { const onSelectChatToShareTo = (conversation: string) => {
logger.metric('share:press:dmSelected', {}, {statsig: true}) ax.metric('share:press:dmSelected', {})
navigation.navigate('MessagesConversation', { navigation.navigate('MessagesConversation', {
conversation, conversation,
embed: postUri, embed: postUri,
@@ -102,7 +103,7 @@ let ShareMenuItems = ({
testID="postDropdownSendViaDMBtn" testID="postDropdownSendViaDMBtn"
label={_(msg`Send via direct message`)} label={_(msg`Send via direct message`)}
onPress={() => { onPress={() => {
logger.metric('share:press:openDmSearch', {}, {statsig: true}) ax.metric('share:press:openDmSearch', {})
sendViaChatControl.open() sendViaChatControl.open()
}}> }}>
<Menu.ItemText> <Menu.ItemText>
@@ -117,7 +118,7 @@ let ShareMenuItems = ({
testID="postDropdownEmbedBtn" testID="postDropdownEmbedBtn"
label={_(msg`Embed post`)} label={_(msg`Embed post`)}
onPress={() => { onPress={() => {
logger.metric('share:press:embed', {}, {statsig: true}) ax.metric('share:press:embed', {})
embedPostControl.open() embedPostControl.open()
}}> }}>
<Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
+11 -13
View File
@@ -13,7 +13,6 @@ import {useLingui} from '@lingui/react'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/post-shadow' import {type Shadow} from '#/state/cache/post-shadow'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {EventStopper} from '#/view/com/util/EventStopper' 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 {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight'
import {useMenuControl} from '#/components/Menu' import {useMenuControl} from '#/components/Menu'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {useAnalytics} from '#/analytics'
import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
import {ShareMenuItems} from './ShareMenuItems' import {ShareMenuItems} from './ShareMenuItems'
@@ -47,6 +47,7 @@ let ShareMenuButton = ({
hitSlop?: Insets hitSlop?: Insets
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
}): React.ReactNode => { }): React.ReactNode => {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {feedDescriptor} = useFeedFeedbackContext() const {feedDescriptor} = useFeedFeedbackContext()
@@ -61,20 +62,17 @@ let ShareMenuButton = ({
// menuControl.open() fires but RN doesn't expose flushSync. // menuControl.open() fires but RN doesn't expose flushSync.
setTimeout(menuControl.open) setTimeout(menuControl.open)
logger.metric( ax.metric('post:share', {
'post:share', uri: post.uri,
{ authorDid: post.author.did,
uri: post.uri, logContext,
authorDid: post.author.did, feedDescriptor,
logContext, postContext: big ? 'thread' : 'feed',
feedDescriptor, })
postContext: big ? 'thread' : 'feed',
},
{statsig: true},
)
}, },
}), }),
[ [
ax,
menuControl, menuControl,
setHasBeenOpen, setHasBeenOpen,
big, big,
@@ -86,7 +84,7 @@ let ShareMenuButton = ({
) )
const onNativeLongPress = () => { const onNativeLongPress = () => {
logger.metric('share:press:nativeShare', {}, {statsig: true}) ax.metric('share:press:nativeShare', {})
const urip = new AtUri(post.uri) const urip = new AtUri(post.uri)
const href = makeProfileLink(post.author, 'post', urip.rkey) const href = makeProfileLink(post.author, 'post', urip.rkey)
const url = toShareUrl(href) const url = toShareUrl(href)
+4 -3
View File
@@ -13,7 +13,6 @@ import {CountWheel} from '#/lib/custom-animations/CountWheel'
import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon' import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
import { import {
@@ -30,6 +29,7 @@ import {atoms as a, useBreakpoints} from '#/alf'
import {Reply as Bubble} from '#/components/icons/Reply' import {Reply as Bubble} from '#/components/icons/Reply'
import {useFormatPostStatCount} from '#/components/PostControls/util' import {useFormatPostStatCount} from '#/components/PostControls/util'
import * as Skele from '#/components/Skeleton' import * as Skele from '#/components/Skeleton'
import {useAnalytics} from '#/analytics'
import {BookmarkButton} from './BookmarkButton' import {BookmarkButton} from './BookmarkButton'
import { import {
PostControlButton, PostControlButton,
@@ -71,6 +71,7 @@ let PostControls = ({
viaRepost?: {uri: string; cid: string} viaRepost?: {uri: string; cid: string}
variant?: 'compact' | 'normal' | 'large' variant?: 'compact' | 'normal' | 'large'
}): React.ReactNode => { }): React.ReactNode => {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const {feedDescriptor} = useFeedFeedbackContext() const {feedDescriptor} = useFeedFeedbackContext()
@@ -175,7 +176,7 @@ let PostControls = ({
feedContext, feedContext,
reqId, reqId,
}) })
logger.metric('post:clickQuotePost', { ax.metric('post:clickQuotePost', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext, logContext,
@@ -226,7 +227,7 @@ let PostControls = ({
!replyDisabled !replyDisabled
? () => ? () =>
requireAuth(() => { requireAuth(() => {
logger.metric('post:clickReply', { ax.metric('post:clickReply', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext, logContext,
+5 -3
View File
@@ -19,6 +19,7 @@ import {FloppyDisk_Stroke2_Corner0_Rounded as FloppyDiskIcon} from '#/components
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {QrCode} from '#/components/StarterPack/QrCode' import {QrCode} from '#/components/StarterPack/QrCode'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
@@ -32,6 +33,7 @@ export function QrCodeDialog({
control: DialogControlProps control: DialogControlProps
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const [isSaveProcessing, setIsSaveProcessing] = useState(false) const [isSaveProcessing, setIsSaveProcessing] = useState(false)
const [isCopyProcessing, setIsCopyProcessing] = useState(false) const [isCopyProcessing, setIsCopyProcessing] = useState(false)
@@ -104,7 +106,7 @@ export function QrCodeDialog({
link.click() link.click()
} }
logger.metric('starterPack:share', { ax.metric('starterPack:share', {
starterPack: starterPack.uri, starterPack: starterPack.uri,
shareType: 'qrcode', shareType: 'qrcode',
qrShareType: 'save', qrShareType: 'save',
@@ -129,7 +131,7 @@ export function QrCodeDialog({
navigator.clipboard.write([item]) navigator.clipboard.write([item])
}) })
logger.metric('starterPack:share', { ax.metric('starterPack:share', {
starterPack: starterPack.uri, starterPack: starterPack.uri,
shareType: 'qrcode', shareType: 'qrcode',
qrShareType: 'copy', qrShareType: 'copy',
@@ -145,7 +147,7 @@ export function QrCodeDialog({
control.close(() => { control.close(() => {
Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then( Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then(
() => { () => {
logger.metric('starterPack:share', { ax.metric('starterPack:share', {
starterPack: starterPack.uri, starterPack: starterPack.uri,
shareType: 'qrcode', shareType: 'qrcode',
qrShareType: 'share', qrShareType: 'share',
+3 -2
View File
@@ -7,7 +7,6 @@ import {useLingui} from '@lingui/react'
import {useSaveImageToMediaLibrary} from '#/lib/media/save-image' import {useSaveImageToMediaLibrary} from '#/lib/media/save-image'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {getStarterPackOgCard} from '#/lib/strings/starter-pack' import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
import {logger} from '#/logger'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {type DialogControlProps} from '#/components/Dialog' 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 {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
interface Props { interface Props {
@@ -46,6 +46,7 @@ function ShareDialogInner({
control, control,
}: Props) { }: Props) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const t = useTheme() const t = useTheme()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
@@ -54,7 +55,7 @@ function ShareDialogInner({
const onShareLink = async () => { const onShareLink = async () => {
if (!link) return if (!link) return
shareUrl(link) shareUrl(link)
logger.metric('starterPack:share', { ax.metric('starterPack:share', {
starterPack: starterPack.uri, starterPack: starterPack.uri,
shareType: 'link', shareType: 'link',
}) })
@@ -13,7 +13,6 @@ import {useLingui} from '@lingui/react'
import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants' import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import { import {
@@ -25,6 +24,7 @@ import {Button, ButtonText} from '#/components/Button'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {Checkbox} from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
function WizardListCard({ function WizardListCard({
@@ -130,6 +130,7 @@ export function WizardProfileCard({
profile: bsky.profile.AnyProfileView profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}) { }) {
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
// Determine the "main" profile for this starter pack - either targetDid or current account // 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 (profile.did === targetProfileDid) return
if (!included) { if (!included) {
logger.metric('starterPack:addUser', {}) ax.metric('starterPack:addUser', {})
dispatch({type: 'AddProfile', profile}) dispatch({type: 'AddProfile', profile})
} else { } else {
logger.metric('starterPack:removeUser', {}) ax.metric('starterPack:removeUser', {})
dispatch({type: 'RemoveProfile', profileDid: profile.did}) dispatch({type: 'RemoveProfile', profileDid: profile.did})
} }
} }
+8 -6
View File
@@ -5,13 +5,13 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {FocusGuards, FocusScope} from 'radix-ui/internal' import {FocusGuards, FocusScope} from 'radix-ui/internal'
import {logger} from '#/logger'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
import {atoms as a, flatten, useBreakpoints, web} from '#/alf' import {atoms as a, flatten, useBreakpoints, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
const welcomeModalBg = require('../../assets/images/welcome-modal-bg.jpg') const welcomeModalBg = require('../../assets/images/welcome-modal-bg.jpg')
@@ -25,6 +25,7 @@ interface WelcomeModalProps {
export function WelcomeModal({control}: WelcomeModalProps) { export function WelcomeModal({control}: WelcomeModalProps) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {requestSwitchToAccount} = useLoggedOutViewControls() const {requestSwitchToAccount} = useLoggedOutViewControls()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const [isExiting, setIsExiting] = useState(false) const [isExiting, setIsExiting] = useState(false)
@@ -40,23 +41,24 @@ export function WelcomeModal({control}: WelcomeModalProps) {
useEffect(() => { useEffect(() => {
if (control.isOpen) { if (control.isOpen) {
logger.metric('welcomeModal:presented', {}) ax.metric('welcomeModal:presented', {})
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [control.isOpen]) }, [control.isOpen])
const onPressCreateAccount = () => { const onPressCreateAccount = () => {
logger.metric('welcomeModal:signupClicked', {}) ax.metric('welcomeModal:signupClicked', {})
control.close() control.close()
requestSwitchToAccount({requestedAccount: 'new'}) requestSwitchToAccount({requestedAccount: 'new'})
} }
const onPressExplore = () => { const onPressExplore = () => {
logger.metric('welcomeModal:exploreClicked', {}) ax.metric('welcomeModal:exploreClicked', {})
fadeOutAndClose() fadeOutAndClose()
} }
const onPressSignIn = () => { const onPressSignIn = () => {
logger.metric('welcomeModal:signinClicked', {}) ax.metric('welcomeModal:signinClicked', {})
control.close() control.close()
requestSwitchToAccount({requestedAccount: 'existing'}) requestSwitchToAccount({requestedAccount: 'existing'})
} }
@@ -222,7 +224,7 @@ export function WelcomeModal({control}: WelcomeModalProps) {
]} ]}
hoverStyle={[a.bg_transparent]} hoverStyle={[a.bg_transparent]}
onPress={() => { onPress={() => {
logger.metric('welcomeModal:dismissed', {}) ax.metric('welcomeModal:dismissed', {})
fadeOutAndClose() fadeOutAndClose()
}} }}
color="secondary" color="secondary"
+5 -4
View File
@@ -17,7 +17,6 @@ import {useLingui} from '@lingui/react'
import {HITSLOP_10} from '#/lib/constants' import {HITSLOP_10} from '#/lib/constants'
import {makeListLink, makeProfileLink} from '#/lib/routes/links' import {makeListLink, makeProfileLink} from '#/lib/routes/links'
import {logger} from '#/logger'
import { import {
type ThreadgateAllowUISetting, type ThreadgateAllowUISetting,
threadgateViewToAllowUISetting, 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 {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
@@ -46,8 +46,9 @@ interface WhoCanReplyProps {
} }
export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
const {_} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui()
const infoDialogControl = useDialogControl() const infoDialogControl = useDialogControl()
const editDialogControl = useDialogControl() const editDialogControl = useDialogControl()
@@ -90,7 +91,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
Keyboard.dismiss() Keyboard.dismiss()
} }
if (isThreadAuthor) { if (isThreadAuthor) {
logger.metric('thread:click:editOwnThreadgate', {}) ax.metric('thread:click:editOwnThreadgate', {})
// wait on prefetch if it manages to resolve in under 200ms // wait on prefetch if it manages to resolve in under 200ms
// otherwise, proceed immediately and show the spinner -sfn // otherwise, proceed immediately and show the spinner -sfn
@@ -101,7 +102,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
editDialogControl.open() editDialogControl.open()
}) })
} else { } else {
logger.metric('thread:click:viewSomeoneElsesThreadgate', {}) ax.metric('thread:click:viewSomeoneElsesThreadgate', {})
infoDialogControl.open() infoDialogControl.open()
} }
@@ -17,13 +17,11 @@ import {
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {updateProfileShadow} from '#/state/cache/profile-shadow' import {updateProfileShadow} from '#/state/cache/profile-shadow'
import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions' import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {platform, useTheme, web} from '#/alf' import {atoms as a, platform, useTheme, web} from '#/alf'
import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition' import {Admonition} from '#/components/Admonition'
import { import {
Button, Button,
@@ -36,6 +34,7 @@ import * as Toggle from '#/components/forms/Toggle'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
@@ -71,6 +70,7 @@ function DialogInner({
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
includeProfile?: boolean includeProfile?: boolean
}) { }) {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const agent = useAgent() const agent = useAgent()
@@ -133,7 +133,7 @@ function DialogInner({
}) })
if (!activitySubscription.post && !activitySubscription.reply) { if (!activitySubscription.post && !activitySubscription.reply) {
logger.metric('activitySubscription:disable', {}) ax.metric('activitySubscription:disable', {})
Toast.show( Toast.show(
_( _(
msg`You will no longer receive notifications for ${sanitizeHandle(profile.handle, '@')}`, msg`You will no longer receive notifications for ${sanitizeHandle(profile.handle, '@')}`,
@@ -160,7 +160,7 @@ function DialogInner({
}, },
) )
} else { } else {
logger.metric('activitySubscription:enable', { ax.metric('activitySubscription:enable', {
setting: activitySubscription.reply ? 'posts_and_replies' : 'posts', setting: activitySubscription.reply ? 'posts_and_replies' : 'posts',
}) })
if (!initialState.post && !initialState.reply) { if (!initialState.post && !initialState.reply) {
@@ -177,7 +177,7 @@ function DialogInner({
}) })
}, },
onError: err => { onError: err => {
logger.error('Could not save activity subscription', {message: err}) ax.logger.error('Could not save activity subscription', {message: err})
}, },
}) })
@@ -20,8 +20,9 @@ import {Divider} from '#/components/Divider'
import {createStaticClick, InlineLinkText} from '#/components/Link' import {createStaticClick, InlineLinkText} from '#/components/Link'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {logger, useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {useDeviceGeolocationApi} from '#/geolocation' import {useDeviceGeolocationApi} from '#/geolocation'
@@ -41,6 +42,7 @@ export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) {
function Inner({style}: ViewStyleProp & {}) { function Inner({style}: ViewStyleProp & {}) {
const t = useTheme() const t = useTheme()
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const ax = useAnalytics()
const control = useDialogControl() const control = useDialogControl()
const appealControl = Dialog.useDialogControl() const appealControl = Dialog.useDialogControl()
const locationControl = Dialog.useDialogControl() const locationControl = Dialog.useDialogControl()
@@ -138,7 +140,7 @@ function Inner({style}: ViewStyleProp & {}) {
label={_(msg`Contact our moderation team`)} label={_(msg`Contact our moderation team`)}
{...createStaticClick(() => { {...createStaticClick(() => {
appealControl.open() appealControl.open()
logger.metric('ageAssurance:appealDialogOpen', {}) ax.metric('ageAssurance:appealDialogOpen', {})
})}> })}>
contact our moderation team contact our moderation team
</InlineLinkText>{' '} </InlineLinkText>{' '}
@@ -167,7 +169,7 @@ function Inner({style}: ViewStyleProp & {}) {
color={hasInitiated ? 'secondary' : 'primary'} color={hasInitiated ? 'secondary' : 'primary'}
onPress={() => { onPress={() => {
control.open() control.open()
logger.metric('ageAssurance:initDialogOpen', { ax.metric('ageAssurance:initDialogOpen', {
hasInitiatedPreviously: hasInitiated, hasInitiatedPreviously: hasInitiated,
}) })
}}> }}>
@@ -10,7 +10,7 @@ import {ShieldCheck_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics'
export function AgeAssuranceAdmonition({ export function AgeAssuranceAdmonition({
children, children,
@@ -40,6 +40,7 @@ function Inner({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
return ( return (
<> <>
@@ -92,7 +93,7 @@ function Inner({
to={'/settings/account'} to={'/settings/account'}
style={[a.text_sm, a.leading_snug, a.font_semi_bold]} style={[a.text_sm, a.leading_snug, a.font_semi_bold]}
onPress={() => { onPress={() => {
logger.metric('ageAssurance:navigateToSettings', {}) ax.metric('ageAssurance:navigateToSettings', {})
}}> }}>
account settings. account settings.
</InlineLinkText> </InlineLinkText>
@@ -15,6 +15,7 @@ import * as Dialog from '#/components/Dialog'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {logger} from '#/ageAssurance' import {logger} from '#/ageAssurance'
import {useAnalytics} from '#/analytics'
export function AgeAssuranceAppealDialog({ export function AgeAssuranceAppealDialog({
control, control,
@@ -37,6 +38,7 @@ export function AgeAssuranceAppealDialog({
function Inner({control}: {control: Dialog.DialogControlProps}) { function Inner({control}: {control: Dialog.DialogControlProps}) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {gtPhone} = useBreakpoints() const {gtPhone} = useBreakpoints()
const agent = useAgent() const agent = useAgent()
@@ -46,7 +48,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) {
const {mutate, isPending} = useMutation({ const {mutate, isPending} = useMutation({
mutationFn: async () => { mutationFn: async () => {
logger.metric('ageAssurance:appealDialogSubmit', {}) ax.metric('ageAssurance:appealDialogSubmit', {})
await agent.createModerationReport( await agent.createModerationReport(
{ {
@@ -12,7 +12,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics'
export function useInternalState() { export function useInternalState() {
const aa = useAgeAssurance() const aa = useAgeAssurance()
@@ -42,6 +42,7 @@ export function useInternalState() {
export function AgeAssuranceDismissibleFeedBanner() { export function AgeAssuranceDismissibleFeedBanner() {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {visible, close} = useInternalState() const {visible, close} = useInternalState()
const copy = useAgeAssuranceCopy() const copy = useAgeAssuranceCopy()
@@ -66,7 +67,7 @@ export function AgeAssuranceDismissibleFeedBanner() {
to="/settings/account" to="/settings/account"
onPress={() => { onPress={() => {
close() close()
logger.metric('ageAssurance:navigateToSettings', {}) ax.metric('ageAssurance:navigateToSettings', {})
}} }}
style={[a.w_full, a.justify_between, a.align_center, a.gap_md]}> style={[a.w_full, a.justify_between, a.align_center, a.gap_md]}>
<View <View
@@ -105,7 +106,7 @@ export function AgeAssuranceDismissibleFeedBanner() {
size="small" size="small"
onPress={() => { onPress={() => {
close() close()
logger.metric('ageAssurance:dismissFeedBanner', {}) ax.metric('ageAssurance:dismissFeedBanner', {})
}} }}
style={[ style={[
a.absolute, a.absolute,
@@ -10,10 +10,11 @@ import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics'
export function AgeAssuranceDismissibleNotice({style}: ViewStyleProp & {}) { export function AgeAssuranceDismissibleNotice({style}: ViewStyleProp & {}) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const aa = useAgeAssurance() const aa = useAgeAssurance()
const {nux} = useNux(Nux.AgeAssuranceDismissibleNotice) const {nux} = useNux(Nux.AgeAssuranceDismissibleNotice)
const copy = useAgeAssuranceCopy() const copy = useAgeAssuranceCopy()
@@ -45,7 +46,7 @@ export function AgeAssuranceDismissibleNotice({style}: ViewStyleProp & {}) {
completed: true, completed: true,
data: undefined, data: undefined,
}) })
logger.metric('ageAssurance:dismissSettingsNotice', {}) ax.metric('ageAssurance:dismissSettingsNotice', {})
}} }}
style={[ style={[
a.absolute, a.absolute,
@@ -19,8 +19,7 @@ import {useSession} from '#/state/session'
import {atoms as a, web} from '#/alf' import {atoms as a, web} from '#/alf'
import {Admonition} from '#/components/Admonition' import {Admonition} from '#/components/Admonition'
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
import {urls} from '#/components/ageAssurance/const' import {KWS_SUPPORTED_LANGS, urls} from '#/components/ageAssurance/const'
import {KWS_SUPPORTED_LANGS} from '#/components/ageAssurance/const'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {Divider} from '#/components/Divider' import {Divider} from '#/components/Divider'
@@ -30,9 +29,9 @@ import {LanguageSelect} from '#/components/LanguageSelect'
import {SimpleInlineLinkText} from '#/components/Link' import {SimpleInlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {logger} from '#/ageAssurance'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance' import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance'
import {useAnalytics} from '#/analytics'
export {useDialogControl} from '#/components/Dialog/context' export {useDialogControl} from '#/components/Dialog/context'
@@ -64,6 +63,7 @@ export function AgeAssuranceInitDialog({
function Inner() { function Inner() {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const cleanError = useCleanError() const cleanError = useCleanError()
@@ -116,7 +116,7 @@ function Inner() {
const onSubmit = async () => { const onSubmit = async () => {
setLanguageError(false) setLanguageError(false)
logger.metric('ageAssurance:initDialogSubmit', {}) ax.metric('ageAssurance:initDialogSubmit', {})
try { try {
const {status} = runEmailValidation() const {status} = runEmailValidation()
@@ -143,7 +143,7 @@ function Inner() {
error = _( error = _(
msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`, 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') { } else if (e.error === 'DidTooLong') {
error = ( error = (
<> <>
@@ -159,14 +159,14 @@ function Inner() {
</Trans> </Trans>
</> </>
) )
logger.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'})
} else { } else {
logger.metric('ageAssurance:initDialogError', {code: 'other'}) ax.metric('ageAssurance:initDialogError', {code: 'other'})
} }
} else { } else {
const {clean, raw} = cleanError(e) const {clean, raw} = cleanError(e)
error = clean || raw || error error = clean || raw || error
logger.metric('ageAssurance:initDialogError', {code: 'other'}) ax.metric('ageAssurance:initDialogError', {code: 'other'})
} }
setError(error) setError(error)
@@ -16,7 +16,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icon
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {refetchAgeAssuranceServerState} from '#/ageAssurance' import {refetchAgeAssuranceServerState} from '#/ageAssurance'
import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
export type AgeAssuranceRedirectDialogState = { export type AgeAssuranceRedirectDialogState = {
@@ -81,6 +81,7 @@ export function AgeAssuranceRedirectDialog() {
export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const agent = useAgent() const agent = useAgent()
const polling = useRef(false) const polling = useRef(false)
@@ -94,7 +95,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
polling.current = true polling.current = true
logger.metric('ageAssurance:redirectDialogOpen', {}) ax.metric('ageAssurance:redirectDialogOpen', {})
wait( wait(
3e3, 3e3,
@@ -125,18 +126,18 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
setSuccess(true) setSuccess(true)
logger.metric('ageAssurance:redirectDialogSuccess', {}) ax.metric('ageAssurance:redirectDialogSuccess', {})
}) })
.catch(() => { .catch(() => {
if (unmounted.current) return if (unmounted.current) return
setError(true) setError(true)
logger.metric('ageAssurance:redirectDialogFail', {}) ax.metric('ageAssurance:redirectDialogFail', {})
}) })
return () => { return () => {
unmounted.current = true unmounted.current = true
} }
}, [agent, control]) }, [ax, agent, control])
if (success) { if (success) {
return ( return (
@@ -13,7 +13,7 @@ import * as Layout from '#/components/Layout'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics'
export function AgeRestrictedScreen({ export function AgeRestrictedScreen({
children, children,
@@ -27,6 +27,7 @@ export function AgeRestrictedScreen({
rightHeaderSlot?: React.ReactNode rightHeaderSlot?: React.ReactNode
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const copy = useAgeAssuranceCopy() const copy = useAgeAssuranceCopy()
const aa = useAgeAssurance() const aa = useAgeAssurance()
@@ -74,7 +75,7 @@ export function AgeRestrictedScreen({
variant="solid" variant="solid"
color="primary" color="primary"
onPress={() => { onPress={() => {
logger.metric('ageAssurance:navigateToSettings', {}) ax.metric('ageAssurance:navigateToSettings', {})
}}> }}>
<ButtonText> <ButtonText>
<Trans>Go to account settings</Trans> <Trans>Go to account settings</Trans>
@@ -6,12 +6,12 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {HITSLOP_10} from '#/lib/constants' import {HITSLOP_10} from '#/lib/constants'
import {logger} from '#/logger'
import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {Link} from '../Link' import {Link} from '../Link'
import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist' import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist'
@@ -19,6 +19,7 @@ import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allow
export function FindContactsBannerNUX() { export function FindContactsBannerNUX() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {visible, close} = useInternalState() const {visible, close} = useInternalState()
if (!visible) return null if (!visible) return null
@@ -30,7 +31,7 @@ export function FindContactsBannerNUX() {
to={{screen: 'FindContactsFlow'}} to={{screen: 'FindContactsFlow'}}
label={_(msg`Import contacts to find your friends`)} label={_(msg`Import contacts to find your friends`)}
onPress={() => { onPress={() => {
logger.metric('contacts:nux:bannerPressed', {}) ax.metric('contacts:nux:bannerPressed', {})
}} }}
style={[ style={[
a.w_full, a.w_full,
@@ -84,6 +85,7 @@ export function FindContactsBannerNUX() {
) )
} }
function useInternalState() { function useInternalState() {
const ax = useAnalytics()
const {nux} = useNux(Nux.FindContactsDismissibleBanner) const {nux} = useNux(Nux.FindContactsDismissibleBanner)
const {mutate: save, variables} = useSaveNux() const {mutate: save, variables} = useSaveNux()
const hidden = !!variables const hidden = !!variables
@@ -103,7 +105,7 @@ function useInternalState() {
completed: true, completed: true,
data: undefined, data: undefined,
}) })
logger.metric('contacts:nux:bannerDismissed', {}) ax.metric('contacts:nux:bannerDismissed', {})
} }
return {visible, close} return {visible, close}
@@ -28,6 +28,7 @@ import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import { import {
contactsWithPhoneNumbersOnly, contactsWithPhoneNumbersOnly,
filterMatchedNumbers, filterMatchedNumbers,
@@ -51,6 +52,7 @@ export function GetContacts({
context: 'Onboarding' | 'Standalone' context: 'Onboarding' | 'Standalone'
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const gutters = useGutters([0, 'wide']) const gutters = useGutters([0, 'wide'])
@@ -100,16 +102,16 @@ export function GetContacts({
}, },
onSuccess: (result, contacts) => { onSuccess: (result, contacts) => {
if (context === 'Onboarding') { if (context === 'Onboarding') {
logger.metric('onboarding:contacts:contactsShared', {}) ax.metric('onboarding:contacts:contactsShared', {})
} }
if (result.matches.length > 0) { if (result.matches.length > 0) {
logger.metric('contacts:import:success', { ax.metric('contacts:import:success', {
contactCount: contacts.length, contactCount: contacts.length,
matchCount: result.matches.length, matchCount: result.matches.length,
entryPoint: context, entryPoint: context,
}) })
} else { } else {
logger.metric('contacts:import:failure', { ax.metric('contacts:import:failure', {
reason: 'noValidNumbers', reason: 'noValidNumbers',
entryPoint: context, entryPoint: context,
}) })
@@ -134,7 +136,7 @@ export function GetContacts({
}) })
}, },
onError: err => { onError: err => {
logger.metric('contacts:import:failure', { ax.metric('contacts:import:failure', {
reason: isNetworkError(err) ? 'networkError' : 'unknown', reason: isNetworkError(err) ? 'networkError' : 'unknown',
entryPoint: context, entryPoint: context,
}) })
@@ -180,7 +182,7 @@ export function GetContacts({
permissions = await Contacts.requestPermissionsAsync() permissions = await Contacts.requestPermissionsAsync()
} }
logger.metric('contacts:permission:request', { ax.metric('contacts:permission:request', {
status: permissions.granted ? 'granted' : 'denied', status: permissions.granted ? 'granted' : 'denied',
accessLevelIOS: ios(permissions.accessPrivileges), accessLevelIOS: ios(permissions.accessPrivileges),
}) })
@@ -31,6 +31,7 @@ import * as Layout from '#/components/Layout'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {useGeolocation} from '#/geolocation' import {useGeolocation} from '#/geolocation'
import {isFindContactsFeatureEnabled} from '../country-allowlist' import {isFindContactsFeatureEnabled} from '../country-allowlist'
import { import {
@@ -52,6 +53,7 @@ export function PhoneInput({
onSkip: () => void onSkip: () => void
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const t = useTheme() const t = useTheme()
const agent = useAgent() const agent = useAgent()
const location = useGeolocation() const location = useGeolocation()
@@ -85,7 +87,7 @@ export function PhoneInput({
payload: {phoneCountryCode, phoneNumber}, payload: {phoneCountryCode, phoneNumber},
}) })
logger.metric('contacts:phone:phoneEntered', {entryPoint: context}) ax.metric('contacts:phone:phoneEntered', {entryPoint: context})
}, },
onMutate: () => { onMutate: () => {
Keyboard.dismiss() Keyboard.dismiss()
@@ -23,6 +23,7 @@ import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {OTPInput} from '../components/OTPInput' import {OTPInput} from '../components/OTPInput'
import {constructFullPhoneNumber, prettyPhoneNumber} from '../phone-number' import {constructFullPhoneNumber, prettyPhoneNumber} from '../phone-number'
import {type Action, type State, useOnPressBackButton} from '../state' import {type Action, type State, useOnPressBackButton} from '../state'
@@ -40,6 +41,7 @@ export function VerifyNumber({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const gutters = useGutters([0, 'wide']) const gutters = useGutters([0, 'wide'])
@@ -83,7 +85,7 @@ export function VerifyNumber({
}) })
}, 1000) }, 1000)
logger.metric('contacts:phone:phoneVerified', {entryPoint: context}) ax.metric('contacts:phone:phoneVerified', {entryPoint: context})
}, },
onMutate: () => setError(null), onMutate: () => setError(null),
onError: err => { onError: err => {
@@ -39,6 +39,7 @@ import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {InviteInfo} from '../components/InviteInfo' import {InviteInfo} from '../components/InviteInfo'
import {type Action, type Contact, type Match, type State} from '../state' import {type Action, type Contact, type Match, type State} from '../state'
@@ -83,6 +84,7 @@ export function ViewMatches({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const gutter = useGutters([0, 'wide']) const gutter = useGutters([0, 'wide'])
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -109,9 +111,9 @@ export function ViewMatches({
const cumulativeFollowCount = useRef(0) const cumulativeFollowCount = useRef(0)
const onFollow = useCallback(() => { const onFollow = useCallback(() => {
logger.metric('contacts:matches:follow', {entryPoint: context}) ax.metric('contacts:matches:follow', {entryPoint: context})
cumulativeFollowCount.current += 1 cumulativeFollowCount.current += 1
}, [context]) }, [ax, context])
const {mutate: followAll, isPending: isFollowingAll} = useMutation({ const {mutate: followAll, isPending: isFollowingAll} = useMutation({
mutationFn: async () => { mutationFn: async () => {
@@ -132,7 +134,7 @@ export function ViewMatches({
return followableDids return followableDids
}, },
onMutate: () => onMutate: () =>
logger.metric('contacts:matches:followAll', { ax.metric('contacts:matches:followAll', {
followCount: followableDids.length, followCount: followableDids.length,
entryPoint: context, entryPoint: context,
}), }),
@@ -218,7 +220,7 @@ export function ViewMatches({
await agent.app.bsky.contact.dismissMatch({subject: did}) await agent.app.bsky.contact.dismissMatch({subject: did})
}, },
onMutate: did => { onMutate: did => {
logger.metric('contacts:matches:dismiss', {entryPoint: context}) ax.metric('contacts:matches:dismiss', {entryPoint: context})
dispatch({type: 'DISMISS_MATCH', payload: {did}}) dispatch({type: 'DISMISS_MATCH', payload: {did}})
}, },
onSuccess: (_res, did) => { onSuccess: (_res, did) => {
@@ -392,7 +394,7 @@ export function ViewMatches({
label={context === 'Onboarding' ? _(msg`Next`) : _(msg`Done`)} label={context === 'Onboarding' ? _(msg`Next`) : _(msg`Done`)}
onPress={() => { onPress={() => {
if (context === 'Onboarding') { if (context === 'Onboarding') {
logger.metric('onboarding:contacts:nextPressed', { ax.metric('onboarding:contacts:nextPressed', {
matchCount: allMatches.length, matchCount: allMatches.length,
followCount: cumulativeFollowCount.current, followCount: cumulativeFollowCount.current,
dismissedMatchCount: state.dismissedMatches.length, dismissedMatchCount: state.dismissedMatches.length,
@@ -516,6 +518,7 @@ function ContactItem({
const gutter = useGutters([0, 'wide']) const gutter = useGutters([0, 'wide'])
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const name = contact.name ?? contact.firstName ?? contact.lastName const name = contact.name ?? contact.firstName ?? contact.lastName
@@ -564,7 +567,7 @@ function ContactItem({
color="secondary" color="secondary"
size="small" size="small"
onPress={async () => { onPress={async () => {
logger.metric('contacts:matches:invite', { ax.metric('contacts:matches:invite', {
entryPoint: context, entryPoint: context,
}) })
try { try {
@@ -11,7 +11,6 @@ import {useQueryClient} from '@tanstack/react-query'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {logger} from '#/logger'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {useMyListsQuery} from '#/state/queries/my-lists' import {useMyListsQuery} from '#/state/queries/my-lists'
import {useGetPost} from '#/state/queries/post' import {useGetPost} from '#/state/queries/post'
@@ -51,6 +50,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico
import {CloseQuote_Stroke2_Corner1_Rounded as QuoteIcon} from '#/components/icons/Quote' import {CloseQuote_Stroke2_Corner1_Rounded as QuoteIcon} from '#/components/icons/Quote'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
export type PostInteractionSettingsFormProps = { export type PostInteractionSettingsFormProps = {
@@ -80,8 +80,9 @@ export function PostInteractionSettingsControlledDialog({
}: PostInteractionSettingsFormProps & { }: PostInteractionSettingsFormProps & {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
}) { }) {
const ax = useAnalytics()
const onClose = useNonReactiveCallback(() => { const onClose = useNonReactiveCallback(() => {
logger.metric('composer:threadgate:save', { ax.metric('composer:threadgate:save', {
hasChanged: !!rest.isDirty, hasChanged: !!rest.isDirty,
persist: !!rest.persist, persist: !!rest.persist,
replyOptions: replyOptions:
@@ -161,6 +162,7 @@ export function PostInteractionSettingsDialog(
export function PostInteractionSettingsDialogControlledInner( export function PostInteractionSettingsDialogControlledInner(
props: PostInteractionSettingsDialogProps, props: PostInteractionSettingsDialogProps,
) { ) {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const [isSaving, setIsSaving] = useState(false) const [isSaving, setIsSaving] = useState(false)
@@ -229,7 +231,7 @@ export function PostInteractionSettingsDialogControlledInner(
props.control.close() props.control.close()
} catch (e: any) { } catch (e: any) {
logger.error(`Failed to save post interaction settings`, { ax.logger.error(`Failed to save post interaction settings`, {
source: 'PostInteractionSettingsDialogControlledInner', source: 'PostInteractionSettingsDialogControlledInner',
safeMessage: e.message, safeMessage: e.message,
}) })
@@ -244,6 +246,7 @@ export function PostInteractionSettingsDialogControlledInner(
} }
}, [ }, [
_, _,
ax,
props.postUri, props.postUri,
props.rootPostUri, props.rootPostUri,
props.control, props.control,
@@ -689,6 +692,7 @@ export function usePrefetchPostInteractionSettings({
postUri: string postUri: string
rootPostUri: string rootPostUri: string
}) { }) {
const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const agent = useAgent()
const getPost = useGetPost() const getPost = useGetPost()
@@ -712,9 +716,9 @@ export function usePrefetchPostInteractionSettings({
}), }),
]) ])
} catch (e: any) { } catch (e: any) {
logger.error(`Failed to prefetch post interaction settings`, { ax.logger.error(`Failed to prefetch post interaction settings`, {
safeMessage: e.message, safeMessage: e.message,
}) })
} }
}, [queryClient, agent, postUri, rootPostUri, getPost]) }, [ax, queryClient, agent, postUri, rootPostUri, getPost])
} }
+5 -5
View File
@@ -1,11 +1,9 @@
import {useCallback, useImperativeHandle, useRef, useState} from 'react' import {useCallback, useImperativeHandle, useRef, useState} from 'react'
import {View} from 'react-native' import {useWindowDimensions, View} from 'react-native'
import {useWindowDimensions} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {BSKY_SERVICE} from '#/lib/constants' import {BSKY_SERVICE} from '#/lib/constants'
import {logger} from '#/logger'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf' import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf'
@@ -17,6 +15,7 @@ import * as TextField from '#/components/forms/TextField'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
type SegmentedControlOptions = typeof BSKY_SERVICE | 'custom' type SegmentedControlOptions = typeof BSKY_SERVICE | 'custom'
@@ -27,6 +26,7 @@ export function ServerInputDialog({
control: Dialog.DialogOuterProps['control'] control: Dialog.DialogOuterProps['control']
onSelect: (url: string) => void onSelect: (url: string) => void
}) { }) {
const ax = useAnalytics()
const {height} = useWindowDimensions() const {height} = useWindowDimensions()
const formRef = useRef<DialogInnerRef>(null) const formRef = useRef<DialogInnerRef>(null)
@@ -43,10 +43,10 @@ export function ServerInputDialog({
setPreviousCustomAddress(result) setPreviousCustomAddress(result)
} }
} }
logger.metric('signin:hostingProviderPressed', { ax.metric('signin:hostingProviderPressed', {
hostingProviderDidChange: fixedOption !== BSKY_SERVICE, hostingProviderDidChange: fixedOption !== BSKY_SERVICE,
}) })
}, [onSelect, fixedOption]) }, [ax, onSelect, fixedOption])
return ( return (
<Dialog.Outer <Dialog.Outer
+5 -4
View File
@@ -11,7 +11,6 @@ import {useQueryClient} from '@tanstack/react-query'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {logger} from '#/logger'
import { import {
invalidateActorStarterPacksWithMembershipQuery, invalidateActorStarterPacksWithMembershipQuery,
useActorStarterPacksWithMembershipsQuery, useActorStarterPacksWithMembershipsQuery,
@@ -31,6 +30,7 @@ import {StarterPack} from '#/components/icons/StarterPack'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
@@ -244,8 +244,9 @@ function StarterPackItem({
starterPackWithMembership: StarterPackWithMembership starterPackWithMembership: StarterPackWithMembership
targetDid: string targetDid: string
}) { }) {
const {_} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const starterPack = starterPackWithMembership.starterPack const starterPack = starterPackWithMembership.starterPack
@@ -304,7 +305,7 @@ function StarterPackItem({
listUri: listUri, listUri: listUri,
actorDid: targetDid, actorDid: targetDid,
}) })
logger.metric('starterPack:addUser', {starterPack: starterPackUri}) ax.metric('starterPack:addUser', {starterPack: starterPackUri})
} else { } else {
if (!starterPackWithMembership.listItem?.uri) { if (!starterPackWithMembership.listItem?.uri) {
console.error('Cannot remove: missing membership URI') console.error('Cannot remove: missing membership URI')
@@ -316,7 +317,7 @@ function StarterPackItem({
actorDid: targetDid, actorDid: targetDid,
membershipUri: starterPackWithMembership.listItem.uri, membershipUri: starterPackWithMembership.listItem.uri,
}) })
logger.metric('starterPack:removeUser', {starterPack: starterPackUri}) ax.metric('starterPack:removeUser', {starterPack: starterPackUri})
} }
} }
@@ -5,7 +5,6 @@ import {LinearGradient} from 'expo-linear-gradient'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import {isFindContactsFeatureEnabled} from '#/components/contacts/country-allowlist' import {isFindContactsFeatureEnabled} from '#/components/contacts/country-allowlist'
@@ -16,8 +15,8 @@ import {
isExistingUserAsOf, isExistingUserAsOf,
} from '#/components/dialogs/nuxs/utils' } from '#/components/dialogs/nuxs/utils'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env' import {useAnalytics} from '#/analytics'
import {IS_E2E} from '#/env' import {IS_E2E, IS_NATIVE, IS_WEB} from '#/env'
import {navigate} from '#/Navigation' import {navigate} from '#/Navigation'
export const enabled = createIsEnabledCheck(props => { export const enabled = createIsEnabledCheck(props => {
@@ -35,6 +34,7 @@ export const enabled = createIsEnabledCheck(props => {
export function FindContactsAnnouncement() { export function FindContactsAnnouncement() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const nuxDialogs = useNuxDialogContext() const nuxDialogs = useNuxDialogContext()
const control = Dialog.useDialogControl() const control = Dialog.useDialogControl()
@@ -115,7 +115,7 @@ export function FindContactsAnnouncement() {
size="large" size="large"
color="primary" color="primary"
onPress={() => { onPress={() => {
logger.metric('contacts:nux:ctaPressed', {}) ax.metric('contacts:nux:ctaPressed', {})
control.close(() => { control.close(() => {
navigate('FindContactsFlow') navigate('FindContactsFlow')
}) })
@@ -5,7 +5,6 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {urls} from '#/lib/constants' import {urls} from '#/lib/constants'
import {logger} from '#/logger'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
@@ -14,11 +13,13 @@ import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons
import {VerifierCheck} from '#/components/icons/VerifierCheck' import {VerifierCheck} from '#/components/icons/VerifierCheck'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Span, Text} from '#/components/Typography' import {Span, Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
export function InitialVerificationAnnouncement() { export function InitialVerificationAnnouncement() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const nuxDialogs = useNuxDialogContext() const nuxDialogs = useNuxDialogContext()
const control = Dialog.useDialogControl() const control = Dialog.useDialogControl()
@@ -161,13 +162,9 @@ export function InitialVerificationAnnouncement() {
color="primary" color="primary"
style={[a.justify_center, a.w_full]} style={[a.justify_center, a.w_full]}
onPress={() => { onPress={() => {
logger.metric( ax.metric('verification:learn-more', {
'verification:learn-more', location: 'initialAnnouncementeNux',
{ })
location: 'initialAnnouncementeNux',
},
{statsig: false},
)
}}> }}>
<ButtonText> <ButtonText>
<Trans>Read blog post</Trans> <Trans>Read blog post</Trans>
+8 -11
View File
@@ -7,7 +7,6 @@ import {useLingui} from '@lingui/react'
import {useTranslate} from '#/lib/hooks/useTranslate' import {useTranslate} from '#/lib/hooks/useTranslate'
import {richTextToString} from '#/lib/strings/rich-text-helpers' import {richTextToString} from '#/lib/strings/rich-text-helpers'
import {logger} from '#/logger'
import {useConvoActive} from '#/state/messages/convo' import {useConvoActive} from '#/state/messages/convo'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -22,6 +21,7 @@ import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/War
import {ReportDialog} from '#/components/moderation/ReportDialog' import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {usePromptControl} from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {EmojiReactionPicker} from './EmojiReactionPicker' import {EmojiReactionPicker} from './EmojiReactionPicker'
import {hasReachedReactionLimit} from './util' import {hasReachedReactionLimit} from './util'
@@ -34,6 +34,7 @@ export let MessageContextMenu = ({
children: TriggerProps['children'] children: TriggerProps['children']
}): React.ReactNode => { }): React.ReactNode => {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const convo = useConvoActive() const convo = useConvoActive()
const deleteControl = usePromptControl() const deleteControl = usePromptControl()
@@ -60,16 +61,12 @@ export let MessageContextMenu = ({
const onPressTranslateMessage = useCallback(() => { const onPressTranslateMessage = useCallback(() => {
translate(message.text, langPrefs.primaryLanguage) translate(message.text, langPrefs.primaryLanguage)
logger.metric( ax.metric('translate', {
'translate', sourceLanguages: [],
{ targetLanguage: langPrefs.primaryLanguage,
sourceLanguages: [], textLength: message.text.length,
targetLanguage: langPrefs.primaryLanguage, })
textLength: message.text.length, }, [ax, langPrefs.primaryLanguage, message.text, translate])
},
{statsig: false},
)
}, [langPrefs.primaryLanguage, message.text, translate])
const onDelete = useCallback(() => { const onDelete = useCallback(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
+4 -11
View File
@@ -11,7 +11,6 @@ import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {toNiceDomain} from '#/lib/strings/url-helpers' import {toNiceDomain} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {unstableCacheProfileView} from '#/state/queries/profile' import {unstableCacheProfileView} from '#/state/queries/profile'
import {android, atoms as a, platform, tokens, useTheme, web} from '#/alf' import {android, atoms as a, platform, tokens, useTheme, web} from '#/alf'
@@ -22,6 +21,7 @@ import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
import {useGlobalReportDialogControl} from '#/components/moderation/ReportDialog' import {useGlobalReportDialogControl} from '#/components/moderation/ReportDialog'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {Globe_Stroke2_Corner0_Rounded} from '../icons/Globe' import {Globe_Stroke2_Corner0_Rounded} from '../icons/Globe'
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRightIcon} from '../icons/SquareArrowTopRight' import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRightIcon} from '../icons/SquareArrowTopRight'
@@ -103,6 +103,7 @@ export function LiveStatus({
padding?: 'lg' | 'xl' padding?: 'lg' | 'xl'
onPressOpenProfile: () => void onPressOpenProfile: () => void
}) { }) {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -174,11 +175,7 @@ export function LiveStatus({
color="primary" color="primary"
variant="solid" variant="solid"
onPress={() => { onPress={() => {
logger.metric( ax.metric('live:card:watch', {subject: profile.did})
'live:card:watch',
{subject: profile.did},
{statsig: true},
)
openLink(embed.external.uri, false) openLink(embed.external.uri, false)
}}> }}>
<ButtonText> <ButtonText>
@@ -207,11 +204,7 @@ export function LiveStatus({
color="secondary" color="secondary"
variant="solid" variant="solid"
onPress={() => { onPress={() => {
logger.metric( ax.metric('live:card:openProfile', {subject: profile.did})
'live:card:openProfile',
{subject: profile.did},
{statsig: true},
)
unstableCacheProfileView(queryClient, profile) unstableCacheProfileView(queryClient, profile)
onPressOpenProfile() onPressOpenProfile()
}}> }}>
+9 -15
View File
@@ -12,13 +12,13 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {imageToThumb} from '#/lib/api/resolve' import {imageToThumb} from '#/lib/api/resolve'
import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta' import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta'
import {logger} from '#/logger'
import {updateProfileShadow} from '#/state/cache/profile-shadow' import {updateProfileShadow} from '#/state/cache/profile-shadow'
import {useLiveNowConfig} from '#/state/service-config' import {useLiveNowConfig} from '#/state/service-config'
import {useAgent, useSession} from '#/state/session' import {useAgent, useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {useDialogContext} from '#/components/Dialog' import {useDialogContext} from '#/components/Dialog'
import {getLiveServiceNames} from '#/components/live/utils' import {getLiveServiceNames} from '#/components/live/utils'
import {useAnalytics} from '#/analytics'
export function useLiveLinkMetaQuery(url: string | null) { export function useLiveLinkMetaQuery(url: string | null) {
const liveNowConfig = useLiveNowConfig() const liveNowConfig = useLiveNowConfig()
@@ -50,6 +50,7 @@ export function useUpsertLiveStatusMutation(
linkMeta: LinkMeta | null | undefined, linkMeta: LinkMeta | null | undefined,
createdAt?: string, createdAt?: string,
) { ) {
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -77,7 +78,7 @@ export function useUpsertLiveStatusMutation(
thumb = blob.data.blob thumb = blob.data.blob
} }
} catch (e: any) { } catch (e: any) {
logger.error(`Failed to upload thumbnail for live status`, { ax.logger.error(`Failed to upload thumbnail for live status`, {
url: linkMeta.url, url: linkMeta.url,
image: linkMeta.image, image: linkMeta.image,
safeMessage: e, safeMessage: e,
@@ -133,7 +134,7 @@ export function useUpsertLiveStatusMutation(
} }
}, },
onError: (e: any) => { onError: (e: any) => {
logger.error(`Failed to upsert live status`, { ax.logger.error(`Failed to upsert live status`, {
url: linkMeta?.url, url: linkMeta?.url,
image: linkMeta?.image, image: linkMeta?.image,
safeMessage: e, safeMessage: e,
@@ -141,17 +142,9 @@ export function useUpsertLiveStatusMutation(
}, },
onSuccess: ({record, image}) => { onSuccess: ({record, image}) => {
if (createdAt) { if (createdAt) {
logger.metric( ax.metric('live:edit', {duration: record.durationMinutes})
'live:edit',
{duration: record.durationMinutes},
{statsig: true},
)
} else { } else {
logger.metric( ax.metric('live:create', {duration: record.durationMinutes})
'live:create',
{duration: record.durationMinutes},
{statsig: true},
)
} }
Toast.show(_(msg`You are now live!`)) Toast.show(_(msg`You are now live!`))
@@ -187,6 +180,7 @@ export function useUpsertLiveStatusMutation(
} }
export function useRemoveLiveStatusMutation() { export function useRemoveLiveStatusMutation() {
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -203,12 +197,12 @@ export function useRemoveLiveStatusMutation() {
}) })
}, },
onError: (e: any) => { onError: (e: any) => {
logger.error(`Failed to remove live status`, { ax.logger.error(`Failed to remove live status`, {
safeMessage: e, safeMessage: e,
}) })
}, },
onSuccess: () => { onSuccess: () => {
logger.metric('live:remove', {}, {statsig: true}) ax.metric('live:remove', {})
Toast.show(_(msg`You are no longer live`)) Toast.show(_(msg`You are no longer live`))
control.close(() => { control.close(() => {
if (!currentAccount) return if (!currentAccount) return
@@ -7,7 +7,6 @@ import {useLingui} from '@lingui/react'
import {wait} from '#/lib/async/wait' import {wait} from '#/lib/async/wait'
import {getLabelingServiceTitle} from '#/lib/moderation' import {getLabelingServiceTitle} from '#/lib/moderation'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {Logger} from '#/logger'
import {useMyLabelersQuery} from '#/state/queries/preferences' import {useMyLabelersQuery} from '#/state/queries/preferences'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
@@ -28,6 +27,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {createStaticClick, InlineLinkText, Link} from '#/components/Link' import {createStaticClick, InlineLinkText, Link} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {useSubmitReportMutation} from './action' import {useSubmitReportMutation} from './action'
import { import {
@@ -53,8 +53,6 @@ export function useGlobalReportDialogControl() {
return useGlobalDialogsControlContext().reportDialogControl return useGlobalDialogsControlContext().reportDialogControl
} }
const logger = Logger.create(Logger.Context.ReportDialog)
export function GlobalReportDialog() { export function GlobalReportDialog() {
const {value, control} = useGlobalReportDialogControl() const {value, control} = useGlobalReportDialogControl()
return <ReportDialog control={control} subject={value?.subject} /> return <ReportDialog control={control} subject={value?.subject} />
@@ -65,13 +63,14 @@ export function ReportDialog(
subject?: ReportSubject subject?: ReportSubject
}, },
) { ) {
const ax = useAnalytics()
const subject = React.useMemo( const subject = React.useMemo(
() => (props.subject ? parseReportSubject(props.subject) : undefined), () => (props.subject ? parseReportSubject(props.subject) : undefined),
[props.subject], [props.subject],
) )
const onClose = React.useCallback(() => { const onClose = React.useCallback(() => {
logger.metric('reportDialog:close', {}, {statsig: false}) ax.metric('reportDialog:close', {})
}, []) }, [ax])
return ( return (
<Dialog.Outer control={props.control} onClose={onClose}> <Dialog.Outer control={props.control} onClose={onClose}>
<Dialog.Handle /> <Dialog.Handle />
@@ -103,6 +102,8 @@ function Invalid() {
} }
function Inner(props: ReportDialogProps) { function Inner(props: ReportDialogProps) {
const ax = useAnalytics()
const logger = ax.logger.useContext(ax.logger.Context.ReportDialog)
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ref = React.useRef<ScrollView>(null) const ref = React.useRef<ScrollView>(null)
@@ -208,15 +209,11 @@ function Inner(props: ReportDialogProps) {
}), }),
) )
setSuccess(true) setSuccess(true)
logger.metric( ax.metric('reportDialog:success', {
'reportDialog:success', reason: state.selectedOption?.reason ?? '',
{ labeler: state.selectedLabeler?.creator.handle ?? '',
reason: state.selectedOption?.reason ?? '', details: !!state.details,
labeler: state.selectedLabeler?.creator.handle ?? '', })
details: !!state.details,
},
{statsig: false},
)
// give time for user feedback // give time for user feedback
setTimeout(() => { setTimeout(() => {
props.control.close(() => { props.control.close(() => {
@@ -224,7 +221,7 @@ function Inner(props: ReportDialogProps) {
}) })
}, 1e3) }, 1e3)
} catch (e: any) { } catch (e: any) {
logger.metric('reportDialog:failure', {}, {statsig: false}) ax.metric('reportDialog:failure', {})
logger.error(e, { logger.error(e, {
source: 'ReportDialog', source: 'ReportDialog',
}) })
@@ -238,7 +235,7 @@ function Inner(props: ReportDialogProps) {
}, [_, submitReport, state, dispatch, props, setPending, setSuccess]) }, [_, submitReport, state, dispatch, props, setPending, setSuccess])
React.useEffect(() => { React.useEffect(() => {
logger.metric( ax.metric(
'reportDialog:open', 'reportDialog:open',
{ {
subjectType: props.subject.type, subjectType: props.subject.type,
@@ -2,7 +2,6 @@ import {View} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
@@ -12,6 +11,7 @@ import {type FullVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck' import {VerificationCheck} from '#/components/verification/VerificationCheck'
import {VerificationsDialog} from '#/components/verification/VerificationsDialog' import {VerificationsDialog} from '#/components/verification/VerificationsDialog'
import {VerifierDialog} from '#/components/verification/VerifierDialog' import {VerifierDialog} from '#/components/verification/VerifierDialog'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function shouldShowVerificationCheckButton( export function shouldShowVerificationCheckButton(
@@ -77,6 +77,7 @@ export function Badge({
size: 'lg' | 'md' | 'sm' size: 'lg' | 'md' | 'sm'
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const verificationsDialogControl = useDialogControl() const verificationsDialogControl = useDialogControl()
const verifierDialogControl = useDialogControl() const verifierDialogControl = useDialogControl()
@@ -101,7 +102,7 @@ export function Badge({
hitSlop={20} hitSlop={20}
onPress={evt => { onPress={evt => {
evt.preventDefault() evt.preventDefault()
logger.metric('verification:badge:click', {}, {statsig: true}) ax.metric('verification:badge:click', {})
if (state.profile.role === 'verifier') { if (state.profile.role === 'verifier') {
verifierDialogControl.open() verifierDialogControl.open()
} else { } else {
@@ -5,7 +5,6 @@ import {useLingui} from '@lingui/react'
import {urls} from '#/lib/constants' import {urls} from '#/lib/constants'
import {getUserDisplayName} from '#/lib/getUserDisplayName' import {getUserDisplayName} from '#/lib/getUserDisplayName'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -20,6 +19,7 @@ import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type FullVerificationState} from '#/components/verification' import {type FullVerificationState} from '#/components/verification'
import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export {useDialogControl} from '#/components/Dialog' export {useDialogControl} from '#/components/Dialog'
@@ -55,6 +55,7 @@ function Inner({
verificationState: FullVerificationState verificationState: FullVerificationState
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
@@ -158,13 +159,9 @@ function Inner({
color="secondary" color="secondary"
style={[a.justify_center]} style={[a.justify_center]}
onPress={() => { onPress={() => {
logger.metric( ax.metric('verification:learn-more', {
'verification:learn-more', location: 'verificationsDialog',
{ })
location: 'verificationsDialog',
},
{statsig: true},
)
}}> }}>
<ButtonText> <ButtonText>
<Trans context="english-only-resource">Learn more</Trans> <Trans context="english-only-resource">Learn more</Trans>
@@ -5,7 +5,6 @@ import {useLingui} from '@lingui/react'
import {urls} from '#/lib/constants' import {urls} from '#/lib/constants'
import {getUserDisplayName} from '#/lib/getUserDisplayName' import {getUserDisplayName} from '#/lib/getUserDisplayName'
import {logger} from '#/logger'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
@@ -14,6 +13,7 @@ import {VerifierCheck} from '#/components/icons/VerifierCheck'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {type FullVerificationState} from '#/components/verification' import {type FullVerificationState} from '#/components/verification'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export {useDialogControl} from '#/components/Dialog' export {useDialogControl} from '#/components/Dialog'
@@ -49,6 +49,7 @@ function Inner({
verificationState: FullVerificationState verificationState: FullVerificationState
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -126,7 +127,7 @@ function Inner({
color="primary" color="primary"
style={[a.justify_center]} style={[a.justify_center]}
onPress={() => { onPress={() => {
logger.metric( ax.metric(
'verification:learn-more', 'verification:learn-more',
{ {
location: 'verifierDialog', location: 'verifierDialog',
@@ -6,11 +6,11 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isBskyCustomFeedUrl} from '#/lib/strings/url-helpers' import {isBskyCustomFeedUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {atoms as a, utils} from '#/alf' import {atoms as a, utils} from '#/alf'
import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live' import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import { import {
type LiveEventFeed, type LiveEventFeed,
type LiveEventFeedMetricContext, type LiveEventFeedMetricContext,
@@ -26,6 +26,7 @@ export function LiveEventFeedCardCompact({
metricContext: LiveEventFeedMetricContext metricContext: LiveEventFeedMetricContext
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const layout = feed.layouts.compact const layout = feed.layouts.compact
const overlayColor = layout.overlayColor const overlayColor = layout.overlayColor
@@ -39,7 +40,7 @@ export function LiveEventFeedCardCompact({
}, [feed.url]) }, [feed.url])
useEffect(() => { useEffect(() => {
logger.metric('liveEvents:feedBanner:seen', { ax.metric('liveEvents:feedBanner:seen', {
feed: feed.url, feed: feed.url,
context: metricContext, context: metricContext,
}) })
@@ -52,7 +53,7 @@ export function LiveEventFeedCardCompact({
label={_(msg`Live event happening now: ${feed.title}`)} label={_(msg`Live event happening now: ${feed.title}`)}
style={[a.w_full]} style={[a.w_full]}
onPress={() => { onPress={() => {
logger.metric('liveEvents:feedBanner:click', { ax.metric('liveEvents:feedBanner:click', {
feed: feed.url, feed: feed.url,
context: metricContext, context: metricContext,
}) })
@@ -6,10 +6,10 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isBskyCustomFeedUrl} from '#/lib/strings/url-helpers' import {isBskyCustomFeedUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {atoms as a, useBreakpoints, utils} from '#/alf' import {atoms as a, useBreakpoints, utils} from '#/alf'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import { import {
type LiveEventFeed, type LiveEventFeed,
type LiveEventFeedMetricContext, type LiveEventFeedMetricContext,
@@ -24,6 +24,7 @@ export function LiveEventFeedCardWide({
feed: LiveEventFeed feed: LiveEventFeed
metricContext: LiveEventFeedMetricContext metricContext: LiveEventFeedMetricContext
}) { }) {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {gtPhone} = useBreakpoints() const {gtPhone} = useBreakpoints()
@@ -38,8 +39,9 @@ export function LiveEventFeedCardWide({
return '/' return '/'
}, [feed.url]) }, [feed.url])
// TODO double check this
useEffect(() => { useEffect(() => {
logger.metric('liveEvents:feedBanner:seen', { ax.metric('liveEvents:feedBanner:seen', {
feed: feed.url, feed: feed.url,
context: metricContext, context: metricContext,
}) })
@@ -52,7 +54,7 @@ export function LiveEventFeedCardWide({
label={_(msg`Live event happening now: ${feed.title}`)} label={_(msg`Live event happening now: ${feed.title}`)}
style={[a.w_full]} style={[a.w_full]}
onPress={() => { onPress={() => {
logger.metric('liveEvents:feedBanner:click', { ax.metric('liveEvents:feedBanner:click', {
feed: feed.url, feed: feed.url,
context: metricContext, context: metricContext,
}) })
+6 -5
View File
@@ -2,12 +2,12 @@ import {useEffect} from 'react'
import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api' import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
import { import {
preferencesQueryKey, preferencesQueryKey,
usePreferencesQuery, usePreferencesQuery,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import * as env from '#/env' import * as env from '#/env'
import { import {
@@ -63,6 +63,7 @@ export function useUpdateLiveEventPreferences(props: {
undoAction: LiveEventPreferencesAction | null undoAction: LiveEventPreferencesAction | null
}) => void }) => void
}) { }) {
const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const agent = useAgent()
@@ -116,7 +117,7 @@ export function useUpdateLiveEventPreferences(props: {
case 'hideFeed': case 'hideFeed':
case 'unhideFeed': { case 'unhideFeed': {
if (!props.feed) { if (!props.feed) {
logger.error( ax.logger.error(
`useUpdateLiveEventPreferences: feed is missing, but required for hiding/unhiding`, `useUpdateLiveEventPreferences: feed is missing, but required for hiding/unhiding`,
{ {
action, action,
@@ -125,7 +126,7 @@ export function useUpdateLiveEventPreferences(props: {
break break
} }
logger.metric( ax.metric(
action.type === 'hideFeed' action.type === 'hideFeed'
? 'liveEvents:feedBanner:hide' ? 'liveEvents:feedBanner:hide'
: 'liveEvents:feedBanner:unhide', : 'liveEvents:feedBanner:unhide',
@@ -138,11 +139,11 @@ export function useUpdateLiveEventPreferences(props: {
} }
case 'toggleHideAllFeeds': { case 'toggleHideAllFeeds': {
if (prefs!.hideAllFeeds) { if (prefs!.hideAllFeeds) {
logger.metric('liveEvents:hideAllFeedBanners', { ax.metric('liveEvents:hideAllFeedBanners', {
context: props.metricContext, context: props.metricContext,
}) })
} else { } else {
logger.metric('liveEvents:unhideAllFeedBanners', { ax.metric('liveEvents:unhideAllFeedBanners', {
context: props.metricContext, context: props.metricContext,
}) })
} }
+1
View File
@@ -8,6 +8,7 @@ const called: Record<OnceKey, boolean> = {
[OnceKey.PreferencesThread]: false, [OnceKey.PreferencesThread]: false,
} }
// TODO dedupe with runCallbackOnce
export function useCallOnce(key: OnceKey) { export function useCallOnce(key: OnceKey) {
return useCallback( return useCallback(
(cb: () => void) => { (cb: () => void) => {
+4 -2
View File
@@ -5,10 +5,10 @@ import * as WebBrowser from 'expo-web-browser'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {parseLinkingUrl} from '#/lib/parseLinkingUrl' import {parseLinkingUrl} from '#/lib/parseLinkingUrl'
import {logger} from '#/logger'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useCloseAllActiveElements} from '#/state/util' import {useCloseAllActiveElements} from '#/state/util'
import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {useIntentDialogs} from '#/components/intents/IntentDialogs'
import {useAnalytics} from '#/analytics'
import {IS_IOS, IS_NATIVE} from '#/env' import {IS_IOS, IS_NATIVE} from '#/env'
import {Referrer} from '../../../modules/expo-bluesky-swiss-army' import {Referrer} from '../../../modules/expo-bluesky-swiss-army'
import {useApplyPullRequestOTAUpdate} from './useOTAUpdates' import {useApplyPullRequestOTAUpdate} from './useOTAUpdates'
@@ -22,6 +22,7 @@ let previousIntentUrl = ''
export function useIntentHandler() { export function useIntentHandler() {
const incomingUrl = Linking.useLinkingURL() const incomingUrl = Linking.useLinkingURL()
const ax = useAnalytics()
const composeIntent = useComposeIntent() const composeIntent = useComposeIntent()
const verifyEmailIntent = useVerifyEmailIntent() const verifyEmailIntent = useVerifyEmailIntent()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -36,7 +37,7 @@ export function useIntentHandler() {
const referrerInfo = Referrer.getReferrerInfo() const referrerInfo = Referrer.getReferrerInfo()
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
logger.metric('deepLink:referrerReceived', { ax.metric('deepLink:referrerReceived', {
to: url, to: url,
referrer: referrerInfo?.referrer, referrer: referrerInfo?.referrer,
hostname: referrerInfo?.hostname, hostname: referrerInfo?.hostname,
@@ -95,6 +96,7 @@ export function useIntentHandler() {
} }
}, [ }, [
incomingUrl, incomingUrl,
ax,
composeIntent, composeIntent,
verifyEmailIntent, verifyEmailIntent,
currentAccount, currentAccount,
+5 -5
View File
@@ -2,8 +2,7 @@ import {useCallback, useEffect} from 'react'
import {Platform} from 'react-native' import {Platform} from 'react-native'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications' import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications'
import {type AtpAgent} from '@atproto/api' import {type AppBskyNotificationRegisterPush, type AtpAgent} from '@atproto/api'
import {type AppBskyNotificationRegisterPush} from '@atproto/api'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import { import {
@@ -16,8 +15,8 @@ import {isNetworkError} from '#/lib/strings/errors'
import {type SessionAccount, useAgent, useSession} from '#/state/session' import {type SessionAccount, useAgent, useSession} from '#/state/session'
import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {IS_NATIVE} from '#/env' import {useAnalytics} from '#/analytics'
import {IS_DEV} from '#/env' import {IS_DEV, IS_NATIVE} from '#/env'
/** /**
* @private * @private
@@ -227,6 +226,7 @@ export function useNotificationsRegistration() {
} }
export function useRequestNotificationsPermission() { export function useRequestNotificationsPermission() {
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const getAndRegisterPushToken = useGetAndRegisterPushToken() const getAndRegisterPushToken = useGetAndRegisterPushToken()
@@ -251,7 +251,7 @@ export function useRequestNotificationsPermission() {
const res = await Notifications.requestPermissionsAsync() const res = await Notifications.requestPermissionsAsync()
notyLogger.metric(`notifications:request`, { ax.metric(`notifications:request`, {
context: context, context: context,
status: res.status, status: res.status,
}) })
+26 -14
View File
@@ -20,7 +20,6 @@ import {
type CommonNavigatorParams, type CommonNavigatorParams,
type NativeStackScreenProps, type NativeStackScreenProps,
} from '#/lib/routes/types' } from '#/lib/routes/types'
import {logger} from '#/logger'
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery' import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
@@ -37,18 +36,20 @@ import {ListFooter} from '#/components/Lists'
import * as Skele from '#/components/Skeleton' import * as Skele from '#/components/Skeleton'
import * as toast from '#/components/Toast' import * as toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'>
export function BookmarksScreen({}: Props) { export function BookmarksScreen({}: Props) {
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const ax = useAnalytics()
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
setMinimalShellMode(false) setMinimalShellMode(false)
logger.metric('bookmarks:view', {}) ax.metric('bookmarks:view', {})
}, [setMinimalShellMode]), }, [setMinimalShellMode, ax]),
) )
return ( return (
@@ -144,7 +145,7 @@ function BookmarksInner() {
key: bookmark.item.uri, key: bookmark.item.uri,
bookmark: { bookmark: {
...bookmark, ...bookmark,
item: bookmark.item as $Typed<AppBskyFeedDefs.NotFoundPost>, item: bookmark.item,
}, },
}) })
} }
@@ -154,7 +155,7 @@ function BookmarksInner() {
key: bookmark.item.uri, key: bookmark.item.uri,
bookmark: { bookmark: {
...bookmark, ...bookmark,
item: bookmark.item as $Typed<AppBskyFeedDefs.PostView>, item: bookmark.item,
}, },
}) })
} }
@@ -270,6 +271,25 @@ function BookmarkNotFound({
) )
} }
function BookmarkItem({
item,
hideTopBorder,
}: {
item: Extract<ListItem, {type: 'bookmark'}>
hideTopBorder: boolean
}) {
const ax = useAnalytics()
return (
<Post
post={item.bookmark.item}
hideTopBorder={hideTopBorder}
onBeforePress={() => {
ax.metric('bookmarks:post-clicked', {})
}}
/>
)
}
function BookmarksEmpty() { function BookmarksEmpty() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
@@ -301,15 +321,7 @@ function renderItem({item, index}: {item: ListItem; index: number}) {
return <BookmarksEmpty /> return <BookmarksEmpty />
} }
case 'bookmark': { case 'bookmark': {
return ( return <BookmarkItem item={item} hideTopBorder={index === 0} />
<Post
post={item.bookmark.item}
hideTopBorder={index === 0}
onBeforePress={() => {
logger.metric('bookmarks:post-clicked', {})
}}
/>
)
} }
case 'bookmarkNotFound': { case 'bookmarkNotFound': {
return ( return (
@@ -3,7 +3,6 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {urls} from '#/lib/constants' import {urls} from '#/lib/constants'
import {logger} from '#/logger'
import { import {
usePreferencesQuery, usePreferencesQuery,
type UsePreferencesQueryResponse, type UsePreferencesQueryResponse,
@@ -17,9 +16,11 @@ import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/i
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {useAnalytics} from '#/analytics'
export function Screen() { export function Screen() {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const gutters = useGutters(['base']) const gutters = useGutters(['base'])
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
@@ -51,13 +52,9 @@ export function Screen() {
}), }),
)} )}
onPress={() => { onPress={() => {
logger.metric( ax.metric('verification:learn-more', {
'verification:learn-more', location: 'verificationSettings',
{ })
location: 'verificationSettings',
},
{statsig: true},
)
}}> }}>
Learn more here. Learn more here.
</InlineLinkText> </InlineLinkText>
@@ -2,10 +2,10 @@ import {useCallback, useState} from 'react'
import {LayoutAnimationConfig} from 'react-native-reanimated' import {LayoutAnimationConfig} from 'react-native-reanimated'
import {SafeAreaView} from 'react-native-safe-area-context' import {SafeAreaView} from 'react-native-safe-area-context'
import {logger} from '#/logger'
import {FindContactsFlow} from '#/components/contacts/FindContactsFlow' import {FindContactsFlow} from '#/components/contacts/FindContactsFlow'
import {type Action, type State} from '#/components/contacts/state' import {type Action, type State} from '#/components/contacts/state'
import {ScreenTransition} from '#/components/ScreenTransition' import {ScreenTransition} from '#/components/ScreenTransition'
import {useAnalytics} from '#/analytics'
import {useOnboardingInternalState} from '../state' import {useOnboardingInternalState} from '../state'
export function StepFindContacts({ export function StepFindContacts({
@@ -16,6 +16,7 @@ export function StepFindContacts({
flowDispatch: React.ActionDispatch<[Action]> flowDispatch: React.ActionDispatch<[Action]>
}) { }) {
const {dispatch} = useOnboardingInternalState() const {dispatch} = useOnboardingInternalState()
const ax = useAnalytics()
const [transitionDirection, setTransitionDirection] = useState< const [transitionDirection, setTransitionDirection] = useState<
'Forward' | 'Backward' 'Forward' | 'Backward'
@@ -24,10 +25,10 @@ export function StepFindContacts({
const isFinalStep = flowState.step === '4: view matches' const isFinalStep = flowState.step === '4: view matches'
const onSkip = useCallback(() => { const onSkip = useCallback(() => {
if (!isFinalStep) { if (!isFinalStep) {
logger.metric('onboarding:contacts:skipPressed', {}) ax.metric('onboarding:contacts:skipPressed', {})
} }
dispatch({type: 'next'}) dispatch({type: 'next'})
}, [dispatch, isFinalStep]) }, [dispatch, isFinalStep, ax])
const canGoBack = flowState.step === '2: verify number' const canGoBack = flowState.step === '2: verify number'
const onBack = useCallback(() => { const onBack = useCallback(() => {
@@ -9,7 +9,6 @@ import * as bcp47Match from 'bcp-47-match'
import {wait} from '#/lib/async/wait' import {wait} from '#/lib/async/wait'
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
import {logger} from '#/logger'
import {updateProfileShadow} from '#/state/cache/profile-shadow' import {updateProfileShadow} from '#/state/cache/profile-shadow'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
@@ -30,12 +29,14 @@ import {boostInterests, InterestTabs} from '#/components/InterestTabs'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import * as toast from '#/components/Toast' import * as toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {bulkWriteFollows} from '../util' import {bulkWriteFollows} from '../util'
export function StepSuggestedAccounts() { export function StepSuggestedAccounts() {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const t = useTheme() const t = useTheme()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
@@ -92,7 +93,7 @@ export function StepSuggestedAccounts() {
const {mutate: followAll, isPending: isFollowingAll} = useMutation({ const {mutate: followAll, isPending: isFollowingAll} = useMutation({
onMutate: () => { onMutate: () => {
logger.metric('onboarding:suggestedAccounts:followAllPressed', { ax.metric('onboarding:suggestedAccounts:followAllPressed', {
tab: selectedInterest ?? 'all', tab: selectedInterest ?? 'all',
numAccounts: followableDids.length, numAccounts: followableDids.length,
}) })
@@ -132,20 +133,16 @@ export function StepSuggestedAccounts() {
(did: string, position: number) => { (did: string, position: number) => {
if (!seenProfilesRef.current.has(did)) { if (!seenProfilesRef.current.has(did)) {
seenProfilesRef.current.add(did) seenProfilesRef.current.add(did)
logger.metric( ax.metric('suggestedUser:seen', {
'suggestedUser:seen', logContext: 'Onboarding',
{ recId: undefined,
logContext: 'Onboarding', position,
recId: undefined, suggestedDid: did,
position, category: selectedInterest,
suggestedDid: did, })
category: selectedInterest,
},
{statsig: true},
)
} }
}, },
[selectedInterest], [ax, selectedInterest],
) )
return ( return (
@@ -297,6 +294,7 @@ function TabBar({
defaultTabLabel?: string defaultTabLabel?: string
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const interestsDisplayNames = useInterestsDisplayNames() const interestsDisplayNames = useInterestsDisplayNames()
const interests = Object.keys(interestsDisplayNames) const interests = Object.keys(interestsDisplayNames)
.sort(boostInterests(popularInterests)) .sort(boostInterests(popularInterests))
@@ -309,11 +307,7 @@ function TabBar({
selectedInterest || (hideDefaultTab ? interests[0] : 'all') selectedInterest || (hideDefaultTab ? interests[0] : 'all')
} }
onSelectTab={tab => { onSelectTab={tab => {
logger.metric( ax.metric('onboarding:suggestedAccounts:tabPressed', {tab: tab})
'onboarding:suggestedAccounts:tabPressed',
{tab: tab},
{statsig: true},
)
onSelectInterest(tab === 'all' ? null : tab) onSelectInterest(tab === 'all' ? null : tab)
}} }}
interestsDisplayNames={ interestsDisplayNames={
@@ -343,6 +337,7 @@ function SuggestedProfileCard({
onSeen: (did: string, position: number) => void onSeen: (did: string, position: number) => void
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const cardRef = useRef<View>(null) const cardRef = useRef<View>(null)
const hasTrackedRef = useRef(false) const hasTrackedRef = useRef(false)
@@ -403,18 +398,14 @@ function SuggestedProfileCard({
withIcon={false} withIcon={false}
logContext="OnboardingSuggestedAccounts" logContext="OnboardingSuggestedAccounts"
onFollow={() => { onFollow={() => {
logger.metric( ax.metric('suggestedUser:follow', {
'suggestedUser:follow', logContext: 'Onboarding',
{ location: 'Card',
logContext: 'Onboarding', recId: undefined,
location: 'Card', position,
recId: undefined, suggestedDid: profile.did,
position, category,
suggestedDid: profile.did, })
category,
},
{statsig: true},
)
}} }}
/> />
</ProfileCard.Header> </ProfileCard.Header>
@@ -19,6 +19,7 @@ import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Che
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp' const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp'
@@ -30,6 +31,7 @@ export function StarterPackCard({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {gtPhone} = useBreakpoints() const {gtPhone} = useBreakpoints()
const agent = useAgent() const agent = useAgent()
@@ -89,7 +91,7 @@ export function StarterPackCard({
} }
}) })
Toast.show(_(msg`All accounts have been followed!`), {type: 'success'}) Toast.show(_(msg`All accounts have been followed!`), {type: 'success'})
logger.metric('starterPack:followAll', { ax.metric('starterPack:followAll', {
logContext: 'Onboarding', logContext: 'Onboarding',
starterPack: view.uri, starterPack: view.uri,
count: dids.length, count: dids.length,
@@ -2,11 +2,11 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {HITSLOP_10} from '#/lib/constants' import {HITSLOP_10} from '#/lib/constants'
import {logger} from '#/logger'
import {type ThreadPreferences} from '#/state/queries/preferences/useThreadPreferences' import {type ThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider' import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {useAnalytics} from '#/analytics'
export function HeaderDropdown({ export function HeaderDropdown({
sort, sort,
@@ -17,6 +17,7 @@ export function HeaderDropdown({
ThreadPreferences, ThreadPreferences,
'sort' | 'setSort' | 'view' | 'setView' 'sort' | 'setSort' | 'view' | 'setView'
>): React.ReactNode { >): React.ReactNode {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
return ( return (
<Menu.Root> <Menu.Root>
@@ -30,7 +31,7 @@ export function HeaderDropdown({
shape="round" shape="round"
hitSlop={HITSLOP_10} hitSlop={HITSLOP_10}
onPress={() => { onPress={() => {
logger.metric('thread:click:headerMenuOpen', {}) ax.metric('thread:click:headerMenuOpen', {})
onPress() onPress()
}} }}
{...props}> {...props}>
@@ -18,7 +18,6 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {niceDate} from '#/lib/strings/time' import {niceDate} from '#/lib/strings/time'
import {getTranslatorLink, isPostInLanguage} from '#/locale/helpers' import {getTranslatorLink, isPostInLanguage} from '#/locale/helpers'
import {logger} from '#/logger'
import { import {
POST_TOMBSTONE, POST_TOMBSTONE,
type Shadow, type Shadow,
@@ -60,6 +59,7 @@ import * as Skele from '#/components/Skeleton'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
import {WhoCanReply} from '#/components/WhoCanReply' import {WhoCanReply} from '#/components/WhoCanReply'
import {useAnalytics} from '#/analytics'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
export function ThreadItemAnchor({ export function ThreadItemAnchor({
@@ -177,6 +177,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
postSource?: PostSource postSource?: PostSource
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
@@ -281,7 +282,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
]) ])
const onOpenAuthor = () => { const onOpenAuthor = () => {
logger.metric('post:clickthroughAuthor', { ax.metric('post:clickthroughAuthor', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext: 'PostThreadItem', logContext: 'PostThreadItem',
@@ -298,7 +299,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
} }
const onOpenEmbed = () => { const onOpenEmbed = () => {
logger.metric('post:clickthroughEmbed', { ax.metric('post:clickthroughEmbed', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext: 'PostThreadItem', logContext: 'PostThreadItem',
@@ -540,6 +541,7 @@ function ExpandedPostDetails({
isThreadAuthor: boolean isThreadAuthor: boolean
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const translate = useTranslate() const translate = useTranslate()
const isRootPost = !('reply' in post.record) const isRootPost = !('reply' in post.record)
@@ -565,7 +567,7 @@ function ExpandedPostDetails({
AppBskyFeedPost.isRecord, AppBskyFeedPost.isRecord,
) )
) { ) {
logger.metric('translate', { ax.metric('translate', {
sourceLanguages: post.record.langs ?? [], sourceLanguages: post.record.langs ?? [],
targetLanguage: langPrefs.primaryLanguage, targetLanguage: langPrefs.primaryLanguage,
textLength: post.record.text.length, textLength: post.record.text.length,
@@ -574,7 +576,7 @@ function ExpandedPostDetails({
return false return false
}, },
[translate, langPrefs, post], [ax, translate, langPrefs, post],
) )
return ( return (
@@ -2,22 +2,23 @@ import {View} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
export function ThreadItemShowOtherReplies({onPress}: {onPress: () => void}) { export function ThreadItemShowOtherReplies({onPress}: {onPress: () => void}) {
const {_} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui()
const label = _(msg`Show more replies`) const label = _(msg`Show more replies`)
return ( return (
<Button <Button
onPress={() => { onPress={() => {
onPress() onPress()
logger.metric('thread:click:showOtherReplies', {}) ax.metric('thread:click:showOtherReplies', {})
}} }}
label={label}> label={label}>
{({hovered, pressed}) => ( {({hovered, pressed}) => (
+9 -12
View File
@@ -6,7 +6,6 @@ import {Trans} from '@lingui/macro'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
import {logger} from '#/logger'
import {useFeedFeedback} from '#/state/feed-feedback' import {useFeedFeedback} from '#/state/feed-feedback'
import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences' import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences'
import { import {
@@ -44,11 +43,13 @@ import {
import {atoms as a, native, platform, useBreakpoints, web} from '#/alf' import {atoms as a, native, platform, useBreakpoints, web} from '#/alf'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {ListFooter} from '#/components/Lists' import {ListFooter} from '#/components/Lists'
import {useAnalytics} from '#/analytics'
const PARENT_CHUNK_SIZE = 5 const PARENT_CHUNK_SIZE = 5
const CHILDREN_CHUNK_SIZE = 50 const CHILDREN_CHUNK_SIZE = 50
export function PostThread({uri}: {uri: string}) { export function PostThread({uri}: {uri: string}) {
const ax = useAnalytics()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const {hasSession} = useSession() const {hasSession} = useSession()
const initialNumToRender = useInitialNumToRender() const initialNumToRender = useInitialNumToRender()
@@ -84,18 +85,14 @@ export function PostThread({uri}: {uri: string}) {
const post = anchor.value.post const post = anchor.value.post
seenPostUriRef.current = post.uri seenPostUriRef.current = post.uri
logger.metric( ax.metric('post:view', {
'post:view', uri: post.uri,
{ authorDid: post.author.did,
uri: post.uri, logContext: 'Post',
authorDid: post.author.did, feedDescriptor: feedFeedback.feedDescriptor,
logContext: 'Post', })
feedDescriptor: feedFeedback.feedDescriptor,
},
{statsig: false},
)
} }
}, [anchor, feedFeedback.feedDescriptor]) }, [ax, anchor, feedFeedback.feedDescriptor])
// Track post:view events for parent posts and replies (non-anchor posts) // Track post:view events for parent posts and replies (non-anchor posts)
const trackThreadItemView = usePostViewTracking('PostThreadItem') const trackThreadItemView = usePostViewTracking('PostThreadItem')
@@ -14,7 +14,6 @@ import {useLingui} from '@lingui/react'
import {MAX_LABELERS} from '#/lib/constants' import {MAX_LABELERS} from '#/lib/constants'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {isAppLabeler} from '#/lib/moderation' import {isAppLabeler} from '#/lib/moderation'
import {logger} from '#/logger'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' import {useLabelerSubscriptionMutation} from '#/state/queries/labeler'
@@ -34,6 +33,7 @@ import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {ProfileHeaderDisplayName} from './DisplayName' import {ProfileHeaderDisplayName} from './DisplayName'
import {EditProfileDialog} from './EditProfileDialog' import {EditProfileDialog} from './EditProfileDialog'
@@ -61,6 +61,7 @@ let ProfileHeaderLabeler = ({
const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> = const profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> =
useProfileShadow(profileUnshadowed) useProfileShadow(profileUnshadowed)
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
const playHaptic = useHaptics() const playHaptic = useHaptics()
@@ -99,9 +100,9 @@ let ProfileHeaderLabeler = ({
), ),
{type: 'error'}, {type: 'error'},
) )
logger.error(`Failed to toggle labeler like`, {message: e.message}) ax.logger.error(`Failed to toggle labeler like`, {message: e.message})
} }
}, [labeler, playHaptic, likeUri, unlikeMod, likeMod, _]) }, [ax, labeler, playHaptic, likeUri, unlikeMod, likeMod, _])
return ( return (
<ProfileHeaderShell <ProfileHeaderShell
@@ -233,8 +234,9 @@ export function HeaderLabelerButtons({
/** disable the subscribe button */ /** disable the subscribe button */
minimal?: boolean minimal?: boolean
}) { }) {
const {_} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const requireAuth = useRequireAuth() const requireAuth = useRequireAuth()
const playHaptic = useHaptics() const playHaptic = useHaptics()
@@ -264,12 +266,11 @@ export function HeaderLabelerButtons({
subscribe, subscribe,
}) })
logger.metric( ax.metric(
subscribe subscribe
? 'moderation:subscribedToLabeler' ? 'moderation:subscribedToLabeler'
: 'moderation:unsubscribedFromLabeler', : 'moderation:unsubscribedFromLabeler',
{}, {},
{statsig: true},
) )
} catch (e: any) { } catch (e: any) {
reset() reset()
@@ -277,7 +278,7 @@ export function HeaderLabelerButtons({
cantSubscribePrompt.open() cantSubscribePrompt.open()
return return
} }
logger.error(`Failed to subscribe to labeler`, {message: e.message}) ax.logger.error(`Failed to subscribe to labeler`, {message: e.message})
} }
}) })
return ( return (
+6 -12
View File
@@ -18,7 +18,6 @@ import {useActorStatus} from '#/lib/actor-status'
import {BACK_HITSLOP} from '#/lib/constants' import {BACK_HITSLOP} from '#/lib/constants'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useLightboxControls} from '#/state/lightbox' import {useLightboxControls} from '#/state/lightbox'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -34,6 +33,7 @@ import {LiveIndicator} from '#/components/live/LiveIndicator'
import {LiveStatusDialog} from '#/components/live/LiveStatusDialog' import {LiveStatusDialog} from '#/components/live/LiveStatusDialog'
import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' import {LabelsOnMe} from '#/components/moderation/LabelsOnMe'
import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts'
import {useAnalytics} from '#/analytics'
import {IS_IOS} from '#/env' import {IS_IOS} from '#/env'
import {GrowableAvatar} from './GrowableAvatar' import {GrowableAvatar} from './GrowableAvatar'
import {GrowableBanner} from './GrowableBanner' import {GrowableBanner} from './GrowableBanner'
@@ -54,6 +54,7 @@ let ProfileHeaderShell = ({
isPlaceholderProfile, isPlaceholderProfile,
}: React.PropsWithChildren<Props>): React.ReactNode => { }: React.PropsWithChildren<Props>): React.ReactNode => {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {_} = useLingui() const {_} = useLingui()
const {openLightbox} = useLightboxControls() const {openLightbox} = useLightboxControls()
@@ -116,22 +117,14 @@ let ProfileHeaderShell = ({
useEffect(() => { useEffect(() => {
if (live.isActive) { if (live.isActive) {
logger.metric( ax.metric('live:view:profile', {subject: profile.did})
'live:view:profile',
{subject: profile.did},
{statsig: true},
)
} }
}, [live.isActive, profile.did]) }, [ax, live.isActive, profile.did])
const onPressAvi = useCallback(() => { const onPressAvi = useCallback(() => {
if (live.isActive) { if (live.isActive) {
playHaptic('Light') playHaptic('Light')
logger.metric( ax.metric('live:card:open', {subject: profile.did, from: 'profile'})
'live:card:open',
{subject: profile.did, from: 'profile'},
{statsig: true},
)
liveStatusControl.open() liveStatusControl.open()
} else { } else {
const modui = moderation.ui('avatar') const modui = moderation.ui('avatar')
@@ -145,6 +138,7 @@ let ProfileHeaderShell = ({
} }
} }
}, [ }, [
ax,
profile, profile,
moderation, moderation,
_openLightbox, _openLightbox,
@@ -5,8 +5,7 @@ import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {makeProfileLink} from '#/lib/routes/links' import {makeCustomFeedLink, makeProfileLink} from '#/lib/routes/links'
import {makeCustomFeedLink} from '#/lib/routes/links'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
@@ -51,6 +50,7 @@ import {
} from '#/components/moderation/ReportDialog' } from '#/components/moderation/ReportDialog'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
export function ProfileFeedHeaderSkeleton() { export function ProfileFeedHeaderSkeleton() {
@@ -86,6 +86,7 @@ export function ProfileFeedHeaderSkeleton() {
export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) { export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
const t = useTheme() const t = useTheme()
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const ax = useAnalytics()
const {hasSession} = useSession() const {hasSession} = useSession()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const infoControl = Dialog.useDialogControl() const infoControl = Dialog.useDialogControl()
@@ -120,7 +121,7 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
if (savedFeedConfig) { if (savedFeedConfig) {
await removeFeed(savedFeedConfig) await removeFeed(savedFeedConfig)
Toast.show(_(msg`Removed from your feeds`)) Toast.show(_(msg`Removed from your feeds`))
logger.metric('feed:unsave', {feedUrl: info.uri}) ax.metric('feed:unsave', {feedUrl: info.uri})
} else { } else {
await addSavedFeeds([ await addSavedFeeds([
{ {
@@ -130,7 +131,7 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
}, },
]) ])
Toast.show(_(msg`Saved to your feeds`)) Toast.show(_(msg`Saved to your feeds`))
logger.metric('feed:save', {feedUrl: info.uri}) ax.metric('feed:save', {feedUrl: info.uri})
} }
} catch (err) { } catch (err) {
Toast.show( Toast.show(
@@ -158,10 +159,10 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
if (pinned) { if (pinned) {
Toast.show(_(msg`Pinned ${info.displayName} to Home`)) Toast.show(_(msg`Pinned ${info.displayName} to Home`))
logger.metric('feed:pin', {feedUrl: info.uri}) ax.metric('feed:pin', {feedUrl: info.uri})
} else { } else {
Toast.show(_(msg`Unpinned ${info.displayName} from Home`)) Toast.show(_(msg`Unpinned ${info.displayName} from Home`))
logger.metric('feed:unpin', {feedUrl: info.uri}) ax.metric('feed:unpin', {feedUrl: info.uri})
} }
} else { } else {
await addSavedFeeds([ await addSavedFeeds([
@@ -172,7 +173,7 @@ export function ProfileFeedHeader({info}: {info: FeedSourceFeedInfo}) {
}, },
]) ])
Toast.show(_(msg`Pinned ${info.displayName} to Home`)) Toast.show(_(msg`Pinned ${info.displayName} to Home`))
logger.metric('feed:pin', {feedUrl: info.uri}) ax.metric('feed:pin', {feedUrl: info.uri})
} }
} catch (e) { } catch (e) {
Toast.show(_(msg`There was an issue contacting the server`), 'xmark') Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
@@ -388,6 +389,7 @@ function DialogInner({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {hasSession} = useSession() const {hasSession} = useSession()
const playHaptic = useHaptics() const playHaptic = useHaptics()
const control = Dialog.useDialogContext() const control = Dialog.useDialogContext()
@@ -407,11 +409,11 @@ function DialogInner({
if (isLiked && likeUri) { if (isLiked && likeUri) {
await unlikeFeed({uri: likeUri}) await unlikeFeed({uri: likeUri})
setLikeUri('') setLikeUri('')
logger.metric('feed:unlike', {feedUrl: info.uri}) ax.metric('feed:unlike', {feedUrl: info.uri})
} else { } else {
const res = await likeFeed({uri: info.uri, cid: info.cid}) const res = await likeFeed({uri: info.uri, cid: info.cid})
setLikeUri(res.uri) setLikeUri(res.uri)
logger.metric('feed:like', {feedUrl: info.uri}) ax.metric('feed:like', {feedUrl: info.uri})
} }
} catch (err) { } catch (err) {
Toast.show( Toast.show(
@@ -428,7 +430,7 @@ function DialogInner({
playHaptic() playHaptic()
const url = toShareUrl(info.route.href) const url = toShareUrl(info.route.href)
shareUrl(url) shareUrl(url)
logger.metric('feed:share', {feedUrl: info.uri}) ax.metric('feed:share', {feedUrl: info.uri})
}, [info, playHaptic]) }, [info, playHaptic])
const onPressReport = React.useCallback(() => { const onPressReport = React.useCallback(() => {
+4 -10
View File
@@ -21,6 +21,7 @@ import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {MoreOptionsMenu} from './MoreOptionsMenu' import {MoreOptionsMenu} from './MoreOptionsMenu'
import {SubscribeMenu} from './SubscribeMenu' import {SubscribeMenu} from './SubscribeMenu'
@@ -34,6 +35,7 @@ export function Header({
preferences: UsePreferencesQueryResponse preferences: UsePreferencesQueryResponse
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
const isModList = list.purpose === AppBskyGraphDefs.MODLIST const isModList = list.purpose === AppBskyGraphDefs.MODLIST
@@ -96,11 +98,7 @@ export function Header({
try { try {
await muteList({uri: list.uri, mute: false}) await muteList({uri: list.uri, mute: false})
Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
logger.metric( ax.metric('moderation:unsubscribedFromList', {listType: 'mute'})
'moderation:unsubscribedFromList',
{listType: 'mute'},
{statsig: true},
)
} catch { } catch {
Toast.show( Toast.show(
_( _(
@@ -114,11 +112,7 @@ export function Header({
try { try {
await blockList({uri: list.uri, block: false}) await blockList({uri: list.uri, block: false})
Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
logger.metric( ax.metric('moderation:unsubscribedFromList', {listType: 'block'})
'moderation:unsubscribedFromList',
{listType: 'block'},
{statsig: true},
)
} catch { } catch {
Toast.show( Toast.show(
_( _(
@@ -33,6 +33,7 @@ import {
} from '#/components/moderation/ReportDialog' } from '#/components/moderation/ReportDialog'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
export function MoreOptionsMenu({ export function MoreOptionsMenu({
@@ -43,6 +44,7 @@ export function MoreOptionsMenu({
savedFeedConfig?: AppBskyActorDefs.SavedFeed savedFeedConfig?: AppBskyActorDefs.SavedFeed
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const editListDialogControl = useDialogControl() const editListDialogControl = useDialogControl()
const deleteListPromptControl = useDialogControl() const deleteListPromptControl = useDialogControl()
@@ -111,11 +113,7 @@ export function MoreOptionsMenu({
try { try {
await muteList({uri: list.uri, mute: false}) await muteList({uri: list.uri, mute: false})
Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
logger.metric( ax.metric('moderation:unsubscribedFromList', {listType: 'mute'})
'moderation:unsubscribedFromList',
{listType: 'mute'},
{statsig: true},
)
} catch { } catch {
Toast.show( Toast.show(
_( _(
@@ -129,11 +127,7 @@ export function MoreOptionsMenu({
try { try {
await blockList({uri: list.uri, block: false}) await blockList({uri: list.uri, block: false})
Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
logger.metric( ax.metric('moderation:unsubscribedFromList', {listType: 'block'})
'moderation:unsubscribedFromList',
{listType: 'block'},
{statsig: true},
)
} catch { } catch {
Toast.show( Toast.show(
_( _(
@@ -2,7 +2,6 @@ import {type AppBskyGraphDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -12,9 +11,11 @@ import {Loader} from '#/components/Loader'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) { export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const subscribeMutePromptControl = Prompt.usePromptControl() const subscribeMutePromptControl = Prompt.usePromptControl()
const subscribeBlockPromptControl = Prompt.usePromptControl() const subscribeBlockPromptControl = Prompt.usePromptControl()
@@ -29,11 +30,7 @@ export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
try { try {
await muteList({uri: list.uri, mute: true}) await muteList({uri: list.uri, mute: true})
Toast.show(_(msg({message: 'List muted', context: 'toast'}))) Toast.show(_(msg({message: 'List muted', context: 'toast'})))
logger.metric( ax.metric('moderation:subscribedToList', {listType: 'mute'})
'moderation:subscribedToList',
{listType: 'mute'},
{statsig: true},
)
} catch { } catch {
Toast.show( Toast.show(
_( _(
@@ -48,11 +45,7 @@ export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) {
try { try {
await blockList({uri: list.uri, block: true}) await blockList({uri: list.uri, block: true})
Toast.show(_(msg({message: 'List blocked', context: 'toast'}))) Toast.show(_(msg({message: 'List blocked', context: 'toast'})))
logger.metric( ax.metric('moderation:subscribedToList', {listType: 'block'})
'moderation:subscribedToList',
{listType: 'block'},
{statsig: true},
)
} catch { } catch {
Toast.show( Toast.show(
_( _(
@@ -4,7 +4,6 @@ import {type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {PressableScale} from '#/lib/custom-animations/PressableScale' import {PressableScale} from '#/lib/custom-animations/PressableScale'
import {makeCustomFeedLink} from '#/lib/routes/links' import {makeCustomFeedLink} from '#/lib/routes/links'
import {logger} from '#/logger'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf' import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
@@ -13,6 +12,7 @@ import {sizes as iconSizes} from '#/components/icons/common'
import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass' import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text, type TextProps} from '#/components/Typography' import {Text, type TextProps} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
export function Container({ export function Container({
style, style,
@@ -126,6 +126,7 @@ export function SearchButton({
metricsTag: 'suggestedAccounts' | 'suggestedFeeds' metricsTag: 'suggestedAccounts' | 'suggestedFeeds'
onPress?: () => void onPress?: () => void
}) { }) {
const ax = useAnalytics()
return ( return (
<Button <Button
label={label} label={label}
@@ -135,11 +136,7 @@ export function SearchButton({
shape="round" shape="round"
PressableComponent={native(PressableScale)} PressableComponent={native(PressableScale)}
onPress={() => { onPress={() => {
logger.metric( ax.metric('explore:module:searchButtonPress', {module: metricsTag})
'explore:module:searchButtonPress',
{module: metricsTag},
{statsig: true},
)
onPress?.() onPress?.()
}} }}
style={[ style={[
@@ -2,7 +2,6 @@ import {View} from 'react-native'
import {type AppBskyUnspeccedDefs} from '@atproto/api' import {type AppBskyUnspeccedDefs} from '@atproto/api'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {logger} from '#/logger'
import { import {
DEFAULT_LIMIT as RECOMMENDATIONS_COUNT, DEFAULT_LIMIT as RECOMMENDATIONS_COUNT,
useTrendingTopics, useTrendingTopics,
@@ -16,6 +15,7 @@ import {
TrendingTopicSkeleton, TrendingTopicSkeleton,
} from '#/components/TrendingTopics' } from '#/components/TrendingTopics'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
// Note: This module is not currently used and may be removed in the future. // Note: This module is not currently used and may be removed in the future.
@@ -27,6 +27,7 @@ export function ExploreRecommendations() {
function Inner() { function Inner() {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const gutters = useGutters([0, 'compact']) const gutters = useGutters([0, 'compact'])
const {data: trending, error, isLoading} = useTrendingTopics() const {data: trending, error, isLoading} = useTrendingTopics()
const noRecs = !isLoading && !error && !trending?.suggested?.length const noRecs = !isLoading && !error && !trending?.suggested?.length
@@ -88,11 +89,7 @@ function Inner() {
key={topic.link} key={topic.link}
topic={topic} topic={topic}
onPress={() => { onPress={() => {
logger.metric( ax.metric('recommendedTopic:click', {context: 'explore'})
'recommendedTopic:click',
{context: 'explore'},
{statsig: true},
)
}}> }}>
{({hovered}) => ( {({hovered}) => (
<TrendingTopic <TrendingTopic
@@ -9,11 +9,11 @@ import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
import {logger} from '#/logger' import {logger} from '#/logger'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
import {useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {boostInterests, InterestTabs} from '#/components/InterestTabs' import {boostInterests, InterestTabs} from '#/components/InterestTabs'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import {SubtleHover} from '#/components/SubtleHover' import {SubtleHover} from '#/components/SubtleHover'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function useLoadEnoughProfiles({ export function useLoadEnoughProfiles({
@@ -62,6 +62,7 @@ export function SuggestedAccountsTabBar({
defaultTabLabel?: string defaultTabLabel?: string
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const interestsDisplayNames = useInterestsDisplayNames() const interestsDisplayNames = useInterestsDisplayNames()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const personalizedInterests = preferences?.interests?.tags const personalizedInterests = preferences?.interests?.tags
@@ -77,11 +78,7 @@ export function SuggestedAccountsTabBar({
selectedInterest || (hideDefaultTab ? interests[0] : 'all') selectedInterest || (hideDefaultTab ? interests[0] : 'all')
} }
onSelectTab={tab => { onSelectTab={tab => {
logger.metric( ax.metric('explore:suggestedAccounts:tabPressed', {tab: tab})
'explore:suggestedAccounts:tabPressed',
{tab: tab},
{statsig: true},
)
onSelectInterest(tab === 'all' ? null : tab) onSelectInterest(tab === 'all' ? null : tab)
}} }}
interestsDisplayNames={ interestsDisplayNames={
@@ -112,22 +109,19 @@ let SuggestedProfileCard = ({
position: number position: number
}): React.ReactNode => { }): React.ReactNode => {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
return ( return (
<ProfileCard.Link <ProfileCard.Link
profile={profile} profile={profile}
style={[a.flex_1]} style={[a.flex_1]}
onPress={() => { onPress={() => {
logger.metric( ax.metric('suggestedUser:press', {
'suggestedUser:press', logContext: 'Explore',
{ recId,
logContext: 'Explore', position,
recId, suggestedDid: profile.did,
position, category: null,
suggestedDid: profile.did, })
category: null,
},
{statsig: true},
)
}}> }}>
{s => ( {s => (
<> <>
@@ -157,18 +151,14 @@ let SuggestedProfileCard = ({
withIcon={false} withIcon={false}
logContext="ExploreSuggestedAccounts" logContext="ExploreSuggestedAccounts"
onFollow={() => { onFollow={() => {
logger.metric( ax.metric('suggestedUser:follow', {
'suggestedUser:follow', logContext: 'Explore',
{ location: 'Card',
logContext: 'Explore', recId,
location: 'Card', position,
recId, suggestedDid: profile.did,
position, category: null,
suggestedDid: profile.did, })
category: null,
},
{statsig: true},
)
}} }}
/> />
</ProfileCard.Header> </ProfileCard.Header>
@@ -4,7 +4,6 @@ import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api'
import {msg, plural, Trans} from '@lingui/macro' import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useTrendingSettings} from '#/state/preferences/trending' import {useTrendingSettings} from '#/state/preferences/trending'
import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery' import {useGetTrendsQuery} from '#/state/queries/trending/useGetTrendsQuery'
@@ -19,6 +18,7 @@ import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/ic
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {SubtleHover} from '#/components/SubtleHover' import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
const TOPIC_COUNT = 5 const TOPIC_COUNT = 5
@@ -29,6 +29,7 @@ export function ExploreTrendingTopics() {
} }
function Inner() { function Inner() {
const ax = useAnalytics()
const {data: trending, error, isLoading, isRefetching} = useGetTrendsQuery() const {data: trending, error, isLoading, isRefetching} = useGetTrendsQuery()
const noTopics = !isLoading && !error && !trending?.trends?.length const noTopics = !isLoading && !error && !trending?.trends?.length
@@ -44,11 +45,7 @@ function Inner() {
trend={trend} trend={trend}
rank={index + 1} rank={index + 1}
onPress={() => { onPress={() => {
logger.metric( ax.metric('trendingTopic:click', {context: 'explore'})
'trendingTopic:click',
{context: 'explore'},
{statsig: true},
)
}} }}
/> />
))} ))}
@@ -8,7 +8,6 @@ import {useQueryClient} from '@tanstack/react-query'
import {VIDEO_FEED_URI} from '#/lib/constants' import {VIDEO_FEED_URI} from '#/lib/constants'
import {makeCustomFeedLink} from '#/lib/routes/links' import {makeCustomFeedLink} from '#/lib/routes/links'
import {logger} from '#/logger'
import {RQKEY, usePostFeedQuery} from '#/state/queries/post-feed' import {RQKEY, usePostFeedQuery} from '#/state/queries/post-feed'
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
import {atoms as a, tokens, useGutters, useTheme} from '#/alf' import {atoms as a, tokens, useGutters, useTheme} from '#/alf'
@@ -20,6 +19,7 @@ import {
CompactVideoPostCard, CompactVideoPostCard,
CompactVideoPostCardPlaceholder, CompactVideoPostCardPlaceholder,
} from '#/components/VideoPostCard' } from '#/components/VideoPostCard'
import {useAnalytics} from '#/analytics'
const CARD_WIDTH = 100 const CARD_WIDTH = 100
@@ -151,6 +151,7 @@ function VideoCards({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const items = useMemo(() => { const items = useMemo(() => {
return data.pages return data.pages
.flatMap(page => page.slices) .flatMap(page => page.slices)
@@ -177,11 +178,7 @@ function VideoCards({
sourceInterstitial: 'explore', sourceInterstitial: 'explore',
}} }}
onInteract={() => { onInteract={() => {
logger.metric( ax.metric('videoCard:click', {context: 'interstitial:explore'})
'videoCard:click',
{context: 'interstitial:explore'},
{statsig: true},
)
}} }}
/> />
</View> </View>
+11 -6
View File
@@ -47,6 +47,7 @@ import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
import {bulkWriteFollows} from '../Onboarding/util' import {bulkWriteFollows} from '../Onboarding/util'
@@ -54,13 +55,14 @@ import {bulkWriteFollows} from '../Onboarding/util'
type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsSettings'> type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsSettings'>
export function FindContactsSettingsScreen({}: Props) { export function FindContactsSettingsScreen({}: Props) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {data, error, refetch} = useContactsSyncStatusQuery() const {data, error, refetch} = useContactsSyncStatusQuery()
const isFocused = useIsFocused() const isFocused = useIsFocused()
useEffect(() => { useEffect(() => {
if (data && isFocused) { if (data && isFocused) {
logger.metric('contacts:settings:presented', { ax.metric('contacts:settings:presented', {
hasPreviouslySynced: !!data.syncStatus, hasPreviouslySynced: !!data.syncStatus,
matchCount: data.syncStatus?.matchesCount, matchCount: data.syncStatus?.matchesCount,
}) })
@@ -197,7 +199,7 @@ function SyncStatus({
await agent.app.bsky.contact.dismissMatch({subject: did}) await agent.app.bsky.contact.dismissMatch({subject: did})
}, },
onMutate: async (did: string) => { onMutate: async (did: string) => {
logger.metric('contacts:settings:dismiss', {}) ax.metric('contacts:settings:dismiss', {})
optimisticRemoveMatch(queryClient, did) optimisticRemoveMatch(queryClient, did)
}, },
onError: err => { onError: err => {
@@ -278,6 +280,7 @@ function MatchItem({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const shadow = useProfileShadow(profile) const shadow = useProfileShadow(profile)
return ( return (
@@ -314,7 +317,7 @@ function MatchItem({
profile={profile} profile={profile}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
logContext="FindContacts" logContext="FindContacts"
onFollow={() => logger.metric('contacts:settings:follow', {})} onFollow={() => ax.metric('contacts:settings:follow', {})}
/> />
{!shadow.viewer?.following && ( {!shadow.viewer?.following && (
<Button <Button
@@ -343,6 +346,7 @@ function StatusHeader({
isAnyUnfollowed: boolean isAnyUnfollowed: boolean
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -374,7 +378,7 @@ function StatusHeader({
} }
} while (cursor) } while (cursor)
logger.metric('contacts:settings:followAll', { ax.metric('contacts:settings:followAll', {
followCount: didsToFollow.length, followCount: didsToFollow.length,
}) })
@@ -459,6 +463,7 @@ function StatusHeader({
function StatusFooter({syncedAt}: {syncedAt: string}) { function StatusFooter({syncedAt}: {syncedAt: string}) {
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -466,7 +471,7 @@ function StatusFooter({syncedAt}: {syncedAt: string}) {
mutationFn: async () => { mutationFn: async () => {
await agent.app.bsky.contact.removeData({}) await agent.app.bsky.contact.removeData({})
}, },
onMutate: () => logger.metric('contacts:settings:removeData', {}), onMutate: () => ax.metric('contacts:settings:removeData', {}),
onSuccess: () => { onSuccess: () => {
Toast.show(_(msg`Contacts removed`)) Toast.show(_(msg`Contacts removed`))
queryClient.setQueryData<AppBskyContactGetSyncStatus.OutputSchema>( queryClient.setQueryData<AppBskyContactGetSyncStatus.OutputSchema>(
@@ -520,7 +525,7 @@ function StatusFooter({syncedAt}: {syncedAt: string}) {
(Date.now() - new Date(syncedAt).getTime()) / (Date.now() - new Date(syncedAt).getTime()) /
(1000 * 60 * 60 * 24), (1000 * 60 * 60 * 24),
) )
logger.metric('contacts:settings:resync', { ax.metric('contacts:settings:resync', {
daysSinceLastSync, daysSinceLastSync,
}) })
}} }}
@@ -4,12 +4,12 @@ import {type AppBskyNotificationDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings' import {useNotificationSettingsUpdateMutation} from '#/state/queries/notifications/settings'
import {atoms as a, platform, useTheme} from '#/alf' import {atoms as a, platform, useTheme} from '#/alf'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {Divider} from '../../components/SettingsList' import {Divider} from '../../components/SettingsList'
export function PreferenceControls({ export function PreferenceControls({
@@ -61,6 +61,7 @@ export function Inner({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {mutate} = useNotificationSettingsUpdateMutation() const {mutate} = useNotificationSettingsUpdateMutation()
const channels = useMemo(() => { const channels = useMemo(() => {
@@ -77,7 +78,7 @@ export function Inner({
push: change.includes('push'), push: change.includes('push'),
} satisfies typeof preference } satisfies typeof preference
logger.metric('activityPreference:changeChannels', { ax.metric('activityPreference:changeChannels', {
name, name,
push: newPreference.push, push: newPreference.push,
list: newPreference.list, list: newPreference.list,
@@ -98,7 +99,7 @@ export function Inner({
include: change, include: change,
} satisfies typeof preference } satisfies typeof preference
logger.metric('activityPreference:changeFilter', {name, value: change}) ax.metric('activityPreference:changeFilter', {name, value: change})
mutate({ mutate({
[name]: newPreference, [name]: newPreference,
+7 -6
View File
@@ -11,8 +11,8 @@ import {useSignupContext} from '#/screens/Signup/state'
import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {FormError} from '#/components/forms/FormError' import {FormError} from '#/components/forms/FormError'
import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' import {useAnalytics} from '#/analytics'
import {GCP_PROJECT_ID} from '#/env' import {GCP_PROJECT_ID, IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
import {BackNextButtons} from '../BackNextButtons' import {BackNextButtons} from '../BackNextButtons'
const CAPTCHA_PATH = const CAPTCHA_PATH =
@@ -71,6 +71,7 @@ function StepCaptchaInner({
payload?: string payload?: string
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const theme = useTheme() const theme = useTheme()
const {state, dispatch} = useSignupContext() const {state, dispatch} = useSignupContext()
@@ -109,13 +110,13 @@ function StepCaptchaInner({
const onSuccess = React.useCallback( const onSuccess = React.useCallback(
(code: string) => { (code: string) => {
setCompleted(true) setCompleted(true)
logger.metric('signup:captchaSuccess', {}, {statsig: true}) ax.metric('signup:captchaSuccess', {})
dispatch({ dispatch({
type: 'submit', type: 'submit',
task: {verificationCode: code, mutableProcessed: false}, task: {verificationCode: code, mutableProcessed: false},
}) })
}, },
[dispatch], [ax, dispatch],
) )
const onError = React.useCallback( const onError = React.useCallback(
@@ -124,13 +125,13 @@ function StepCaptchaInner({
type: 'setError', type: 'setError',
value: _(msg`Error receiving captcha response.`), value: _(msg`Error receiving captcha response.`),
}) })
logger.metric('signup:captchaFailure', {}, {statsig: true}) ax.metric('signup:captchaFailure', {})
logger.error('Signup Flow Error', { logger.error('Signup Flow Error', {
registrationHandle: state.handle, registrationHandle: state.handle,
error, error,
}) })
}, },
[_, dispatch, state.handle], [_, ax, dispatch, state.handle],
) )
const onBackPress = React.useCallback(() => { const onBackPress = React.useCallback(() => {
+13 -16
View File
@@ -26,11 +26,13 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {At_Stroke2_Corner0_Rounded as AtIcon} from '#/components/icons/At' import {At_Stroke2_Corner0_Rounded as AtIcon} from '#/components/icons/At'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {BackNextButtons} from '../BackNextButtons' import {BackNextButtons} from '../BackNextButtons'
import {HandleSuggestions} from './HandleSuggestions' import {HandleSuggestions} from './HandleSuggestions'
export function StepHandle() { export function StepHandle() {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const t = useTheme() const t = useTheme()
const {state, dispatch} = useSignupContext() const {state, dispatch} = useSignupContext()
const [draftValue, setDraftValue] = useState(state.handle) const [draftValue, setDraftValue] = useState(state.handle)
@@ -68,16 +70,19 @@ export function StepHandle() {
const {available: handleAvailable} = await checkHandleAvailability( const {available: handleAvailable} = await checkHandleAvailability(
createFullHandle(handle, state.userDomain), createFullHandle(handle, state.userDomain),
state.serviceDescription?.did ?? 'UNKNOWN', state.serviceDescription?.did ?? 'UNKNOWN',
{typeahead: false}, {},
) )
if (!handleAvailable) { if (!handleAvailable) {
ax.metric('signup:handleTaken', {typeahead: false})
dispatch({ dispatch({
type: 'setError', type: 'setError',
value: _(msg`That username is already taken`), value: _(msg`That username is already taken`),
field: 'handle', field: 'handle',
}) })
return return
} else {
ax.metric('signup:handleAvailable', {typeahead: false})
} }
} catch (error) { } catch (error) {
logger.error('Failed to check handle availability on next press', { logger.error('Failed to check handle availability on next press', {
@@ -88,15 +93,11 @@ export function StepHandle() {
dispatch({type: 'setIsLoading', value: false}) dispatch({type: 'setIsLoading', value: false})
} }
logger.metric( ax.metric('signup:nextPressed', {
'signup:nextPressed', activeStep: state.activeStep,
{ phoneVerificationRequired:
activeStep: state.activeStep, state.serviceDescription?.phoneVerificationRequired,
phoneVerificationRequired: })
state.serviceDescription?.phoneVerificationRequired,
},
{statsig: true},
)
// phoneVerificationRequired is actually whether a captcha is required // phoneVerificationRequired is actually whether a captcha is required
if (!state.serviceDescription?.phoneVerificationRequired) { if (!state.serviceDescription?.phoneVerificationRequired) {
dispatch({ dispatch({
@@ -115,11 +116,7 @@ export function StepHandle() {
value: handle, value: handle,
}) })
dispatch({type: 'prev'}) dispatch({type: 'prev'})
logger.metric( ax.metric('signup:backPressed', {activeStep: state.activeStep})
'signup:backPressed',
{activeStep: state.activeStep},
{statsig: true},
)
} }
const hasDebounceSettled = draftValue === debouncedDraftValue const hasDebounceSettled = draftValue === debouncedDraftValue
@@ -202,7 +199,7 @@ export function StepHandle() {
state.userDomain.length * -1, state.userDomain.length * -1,
), ),
) )
logger.metric('signup:handleSuggestionSelected', { ax.metric('signup:handleSuggestionSelected', {
method: suggestion.method, method: suggestion.method,
}) })
}} }}
+5 -7
View File
@@ -30,6 +30,7 @@ import {
MIN_ACCESS_AGE, MIN_ACCESS_AGE,
useAgeAssuranceRegionConfigWithFallback, useAgeAssuranceRegionConfigWithFallback,
} from '#/ageAssurance/util' } from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import { import {
useDeviceGeolocationApi, useDeviceGeolocationApi,
@@ -59,6 +60,7 @@ export function StepInfo({
isLoadingStarterPack: boolean isLoadingStarterPack: boolean
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {state, dispatch} = useSignupContext() const {state, dispatch} = useSignupContext()
const preemptivelyCompleteActivePolicyUpdate = const preemptivelyCompleteActivePolicyUpdate =
usePreemptivelyCompleteActivePolicyUpdate() usePreemptivelyCompleteActivePolicyUpdate()
@@ -165,13 +167,9 @@ export function StepInfo({
dispatch({type: 'setEmail', value: email}) dispatch({type: 'setEmail', value: email})
dispatch({type: 'setPassword', value: password}) dispatch({type: 'setPassword', value: password})
dispatch({type: 'next'}) dispatch({type: 'next'})
logger.metric( ax.metric('signup:nextPressed', {
'signup:nextPressed', activeStep: state.activeStep,
{ })
activeStep: state.activeStep,
},
{statsig: true},
)
} }
return ( return (
+12 -18
View File
@@ -21,8 +21,7 @@ import {
useSafeAreaFrame, useSafeAreaFrame,
useSafeAreaInsets, useSafeAreaInsets,
} from 'react-native-safe-area-context' } from 'react-native-safe-area-context'
import {useEvent} from 'expo' import {useEvent, useEventListener} from 'expo'
import {useEventListener} from 'expo'
import {Image, type ImageStyle} from 'expo-image' import {Image, type ImageStyle} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient' import {LinearGradient} from 'expo-linear-gradient'
import {createVideoPlayer, type VideoPlayer, VideoView} from 'expo-video' import {createVideoPlayer, type VideoPlayer, VideoView} from 'expo-video'
@@ -66,13 +65,12 @@ import {
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import { import {
FeedFeedbackProvider, FeedFeedbackProvider,
useFeedFeedback,
useFeedFeedbackContext, useFeedFeedbackContext,
} from '#/state/feed-feedback' } from '#/state/feed-feedback'
import {useFeedFeedback} from '#/state/feed-feedback'
import {useFeedInfo} from '#/state/queries/feed' import {useFeedInfo} from '#/state/queries/feed'
import {usePostLikeMutationQueue} from '#/state/queries/post' import {usePostLikeMutationQueue} from '#/state/queries/post'
import { import {
type AuthorFilter,
type FeedPostSliceItem, type FeedPostSliceItem,
usePostFeedQuery, usePostFeedQuery,
} from '#/state/queries/post-feed' } from '#/state/queries/post-feed'
@@ -100,6 +98,7 @@ import * as Hider from '#/components/moderation/Hider'
import {PostControls} from '#/components/PostControls' import {PostControls} from '#/components/PostControls'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_ANDROID} from '#/env' import {IS_ANDROID} from '#/env'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber'
@@ -192,11 +191,9 @@ function Feed() {
const feedDesc = useMemo(() => { const feedDesc = useMemo(() => {
switch (params.type) { switch (params.type) {
case 'feedgen': case 'feedgen':
return `feedgen|${params.uri as string}` as const return `feedgen|${params.uri}` as const
case 'author': case 'author':
return `author|${params.did as string}|${ return `author|${params.did}|${params.filter}` as const
params.filter as AuthorFilter
}` as const
default: default:
throw new Error(`Invalid video feed params ${JSON.stringify(params)}`) throw new Error(`Invalid video feed params ${JSON.stringify(params)}`)
} }
@@ -490,6 +487,7 @@ let VideoItem = ({
feedContext: string | undefined feedContext: string | undefined
reqId: string | undefined reqId: string | undefined
}): React.ReactNode => { }): React.ReactNode => {
const ax = useAnalytics()
const postShadow = usePostShadow(post) const postShadow = usePostShadow(post)
const {width, height} = useSafeAreaFrame() const {width, height} = useSafeAreaFrame()
const {sendInteraction, feedDescriptor} = useFeedFeedbackContext() const {sendInteraction, feedDescriptor} = useFeedFeedbackContext()
@@ -507,16 +505,12 @@ let VideoItem = ({
// Track post:view event // Track post:view event
if (!hasTrackedView.current) { if (!hasTrackedView.current) {
hasTrackedView.current = true hasTrackedView.current = true
logger.metric( ax.metric('post:view', {
'post:view', uri: post.uri,
{ authorDid: post.author.did,
uri: post.uri, logContext: 'ImmersiveVideo',
authorDid: post.author.did, feedDescriptor,
logContext: 'ImmersiveVideo', })
feedDescriptor,
},
{statsig: false},
)
} }
} }
}, [ }, [
+6 -8
View File
@@ -9,7 +9,6 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {PROD_DEFAULT_FEED} from '#/lib/constants' import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {replaceEqualDeep} from '#/lib/functions' import {replaceEqualDeep} from '#/lib/functions'
import {getAge} from '#/lib/strings/time' import {getAge} from '#/lib/strings/time'
import {logger} from '#/logger'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import { import {
DEFAULT_HOME_FEED_PREFS, DEFAULT_HOME_FEED_PREFS,
@@ -24,6 +23,7 @@ import {useAgent} from '#/state/session'
import {saveLabelers} from '#/state/session/agent-config' import {saveLabelers} from '#/state/session/agent-config'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util' import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics'
export * from '#/state/queries/preferences/const' export * from '#/state/queries/preferences/const'
export * from '#/state/queries/preferences/moderation' export * from '#/state/queries/preferences/moderation'
@@ -110,6 +110,7 @@ export function useClearPreferencesMutation() {
} }
export function usePreferencesSetContentLabelMutation() { export function usePreferencesSetContentLabelMutation() {
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -120,11 +121,7 @@ export function usePreferencesSetContentLabelMutation() {
>({ >({
mutationFn: async ({label, visibility, labelerDid}) => { mutationFn: async ({label, visibility, labelerDid}) => {
await agent.setContentLabelPref(label, visibility, labelerDid) await agent.setContentLabelPref(label, visibility, labelerDid)
logger.metric( ax.metric('moderation:changeLabelPreference', {preference: visibility})
'moderation:changeLabelPreference',
{preference: visibility},
{statsig: true},
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -417,6 +414,7 @@ export function useSetActiveProgressGuideMutation() {
} }
export function useSetVerificationPrefsMutation() { export function useSetVerificationPrefsMutation() {
const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const agent = useAgent()
@@ -424,9 +422,9 @@ export function useSetVerificationPrefsMutation() {
mutationFn: async prefs => { mutationFn: async prefs => {
await agent.setVerificationPrefs(prefs) await agent.setVerificationPrefs(prefs)
if (prefs.hideBadges) { if (prefs.hideBadges) {
logger.metric('verification:settings:hideBadges', {}, {statsig: true}) ax.metric('verification:settings:hideBadges', {})
} else { } else {
logger.metric('verification:settings:unHideBadges', {}, {statsig: true}) ax.metric('verification:settings:unHideBadges', {})
} }
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
@@ -3,12 +3,12 @@ import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import {OnceKey, useCallOnce} from '#/lib/hooks/useCallOnce' import {OnceKey, useCallOnce} from '#/lib/hooks/useCallOnce'
import {logger} from '#/logger'
import { import {
usePreferencesQuery, usePreferencesQuery,
useSetThreadViewPreferencesMutation, useSetThreadViewPreferencesMutation,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {type ThreadViewPreferences} from '#/state/queries/preferences/types' import {type ThreadViewPreferences} from '#/state/queries/preferences/types'
import {useAnalytics} from '#/analytics'
import {type Literal} from '#/types/utils' import {type Literal} from '#/types/utils'
export type ThreadSortOption = Literal< export type ThreadSortOption = Literal<
@@ -28,6 +28,7 @@ export type ThreadPreferences = {
export function useThreadPreferences({ export function useThreadPreferences({
save, save,
}: {save?: boolean} = {}): ThreadPreferences { }: {save?: boolean} = {}): ThreadPreferences {
const ax = useAnalytics()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const serverPrefs = preferences?.threadViewPrefs const serverPrefs = preferences?.threadViewPrefs
const once = useCallOnce(OnceKey.PreferencesThread) const once = useCallOnce(OnceKey.PreferencesThread)
@@ -61,7 +62,7 @@ export function useThreadPreferences({
) )
once(() => { once(() => {
logger.metric('thread:preferences:load', { ax.metric('thread:preferences:load', {
sort: serverPrefs.sort, sort: serverPrefs.sort,
view: serverPrefs.lab_treeViewEnabled ? 'tree' : 'linear', view: serverPrefs.lab_treeViewEnabled ? 'tree' : 'linear',
}) })
@@ -76,12 +77,12 @@ export function useThreadPreferences({
try { try {
setIsSaving(true) setIsSaving(true)
await mutateAsync(prefs) await mutateAsync(prefs)
logger.metric('thread:preferences:update', { ax.metric('thread:preferences:update', {
sort: prefs.sort, sort: prefs.sort,
view: prefs.lab_treeViewEnabled ? 'tree' : 'linear', view: prefs.lab_treeViewEnabled ? 'tree' : 'linear',
}) })
} catch (e) { } catch (e) {
logger.error('useThreadPreferences failed to save', { ax.logger.error('useThreadPreferences failed to save', {
safeMessage: e, safeMessage: e,
}) })
} finally { } finally {
@@ -2,12 +2,13 @@ import {type AppBskyActorGetProfile} from '@atproto/api'
import {useMutation} from '@tanstack/react-query' import {useMutation} from '@tanstack/react-query'
import {until} from '#/lib/async/until' import {until} from '#/lib/async/until'
import {logger} from '#/logger'
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
import {useAgent, useSession} from '#/state/session' import {useAgent, useSession} from '#/state/session'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function useVerificationCreateMutation() { export function useVerificationCreateMutation() {
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const updateProfileVerificationCache = useUpdateProfileVerificationCache() const updateProfileVerificationCache = useUpdateProfileVerificationCache()
@@ -46,7 +47,7 @@ export function useVerificationCreateMutation() {
) )
}, },
async onSuccess(_, {profile}) { async onSuccess(_, {profile}) {
logger.metric('verification:create', {}, {statsig: true}) ax.metric('verification:create', {})
await updateProfileVerificationCache({profile}) await updateProfileVerificationCache({profile})
}, },
}) })
@@ -6,12 +6,13 @@ import {
import {useMutation} from '@tanstack/react-query' import {useMutation} from '@tanstack/react-query'
import {until} from '#/lib/async/until' import {until} from '#/lib/async/until'
import {logger} from '#/logger'
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
import {useAgent, useSession} from '#/state/session' import {useAgent, useSession} from '#/state/session'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export function useVerificationsRemoveMutation() { export function useVerificationsRemoveMutation() {
const ax = useAnalytics()
const agent = useAgent() const agent = useAgent()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const updateProfileVerificationCache = useUpdateProfileVerificationCache() const updateProfileVerificationCache = useUpdateProfileVerificationCache()
@@ -56,7 +57,7 @@ export function useVerificationsRemoveMutation() {
) )
}, },
async onSuccess(_, {profile}) { async onSuccess(_, {profile}) {
logger.metric('verification:revoke', {}, {statsig: true}) ax.metric('verification:revoke', {})
await updateProfileVerificationCache({profile}) await updateProfileVerificationCache({profile})
}, },
}) })
@@ -24,6 +24,7 @@ import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Glo
import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group'
import * as Tooltip from '#/components/Tooltip' import * as Tooltip from '#/components/Tooltip'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged' import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged'
@@ -42,6 +43,7 @@ export function ThreadgateBtn({
style?: StyleProp<AnimatedStyle<ViewStyle>> style?: StyleProp<AnimatedStyle<ViewStyle>>
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const control = Dialog.useDialogControl() const control = Dialog.useDialogControl()
const [threadgateNudged, setThreadgateNudged] = useThreadgateNudged() const [threadgateNudged, setThreadgateNudged] = useThreadgateNudged()
const [showTooltip, setShowTooltip] = useState(false) const [showTooltip, setShowTooltip] = useState(false)
@@ -66,7 +68,7 @@ export function ThreadgateBtn({
const [persist, setPersist] = useState(false) const [persist, setPersist] = useState(false)
const onPress = () => { const onPress = () => {
logger.metric('composer:threadgate:open', { ax.metric('composer:threadgate:open', {
nudged: tooltipWasShown, nudged: tooltipWasShown,
}) })
+11 -9
View File
@@ -10,7 +10,6 @@ import {
useVideoLibraryPermission, useVideoLibraryPermission,
} from '#/lib/hooks/usePermissions' } from '#/lib/hooks/usePermissions'
import {openCamera, openUnifiedPicker} from '#/lib/media/picker' import {openCamera, openUnifiedPicker} from '#/lib/media/picker'
import {logger} from '#/logger'
import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile' import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile'
import {MAX_IMAGES} from '#/view/com/composer/state/composer' import {MAX_IMAGES} from '#/view/com/composer/state/composer'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
@@ -21,11 +20,13 @@ import {Camera_Stroke2_Corner0_Rounded as CameraIcon} from '#/components/icons/C
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
import {SubtleHover} from '#/components/SubtleHover' import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
export function ComposerPrompt() { export function ComposerPrompt() {
const {_} = useLingui()
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui()
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const profile = useCurrentAccountProfile() const profile = useCurrentAccountProfile()
const [hover, setHover] = useState(false) const [hover, setHover] = useState(false)
@@ -35,12 +36,12 @@ export function ComposerPrompt() {
const sheetWrapper = useSheetWrapper() const sheetWrapper = useSheetWrapper()
const onPress = useCallback(() => { const onPress = useCallback(() => {
logger.metric('composerPrompt:press', {}) ax.metric('composerPrompt:press', {})
openComposer({}) openComposer({})
}, [openComposer]) }, [ax, openComposer])
const onPressImage = useCallback(async () => { const onPressImage = useCallback(async () => {
logger.metric('composerPrompt:gallery:press', {}) ax.metric('composerPrompt:gallery:press', {})
// On web, open the composer with the gallery picker auto-opening // On web, open the composer with the gallery picker auto-opening
if (!IS_NATIVE) { if (!IS_NATIVE) {
@@ -87,10 +88,11 @@ export function ComposerPrompt() {
} }
} catch (err: any) { } catch (err: any) {
if (!String(err).toLowerCase().includes('cancel')) { if (!String(err).toLowerCase().includes('cancel')) {
logger.warn('Error opening image picker', {error: err}) ax.logger.error('Error opening image picker', {error: err})
} }
} }
}, [ }, [
ax,
openComposer, openComposer,
requestPhotoAccessIfNeeded, requestPhotoAccessIfNeeded,
requestVideoAccessIfNeeded, requestVideoAccessIfNeeded,
@@ -98,7 +100,7 @@ export function ComposerPrompt() {
]) ])
const onPressCamera = useCallback(async () => { const onPressCamera = useCallback(async () => {
logger.metric('composerPrompt:camera:press', {}) ax.metric('composerPrompt:camera:press', {})
try { try {
if (!(await requestCameraAccessIfNeeded())) { if (!(await requestCameraAccessIfNeeded())) {
@@ -126,10 +128,10 @@ export function ComposerPrompt() {
}) })
} catch (err: any) { } catch (err: any) {
if (!String(err).toLowerCase().includes('cancel')) { if (!String(err).toLowerCase().includes('cancel')) {
logger.warn('Error opening camera', {error: err}) ax.logger.error('Error opening camera', {error: err})
} }
} }
}, [openComposer, requestCameraAccessIfNeeded]) }, [ax, openComposer, requestCameraAccessIfNeeded])
if (!profile) { if (!profile) {
return null return null
+3 -2
View File
@@ -12,14 +12,15 @@ import {usePalette} from '#/lib/hooks/usePalette'
import {MagnifyingGlassIcon} from '#/lib/icons' import {MagnifyingGlassIcon} from '#/lib/icons'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {s} from '#/lib/styles' import {s} from '#/lib/styles'
import {logger} from '#/logger'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
export function CustomFeedEmptyState() { export function CustomFeedEmptyState() {
const ax = useAnalytics()
const feedFeedback = useFeedFeedbackContext() const feedFeedback = useFeedFeedbackContext()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const hasLoggedDiscoverEmptyErrorRef = React.useRef(false) const hasLoggedDiscoverEmptyErrorRef = React.useRef(false)
@@ -33,7 +34,7 @@ export function CustomFeedEmptyState() {
!hasLoggedDiscoverEmptyErrorRef.current !hasLoggedDiscoverEmptyErrorRef.current
) { ) {
hasLoggedDiscoverEmptyErrorRef.current = true hasLoggedDiscoverEmptyErrorRef.current = true
logger.metric('feed:discover:emptyError', { ax.metric('feed:discover:emptyError', {
userDid: currentAccount.did, userDid: currentAccount.did,
}) })
} }
+5 -4
View File
@@ -18,7 +18,6 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {countLines} from '#/lib/strings/helpers' import {countLines} from '#/lib/strings/helpers'
import {logger} from '#/logger'
import { import {
POST_TOMBSTONE, POST_TOMBSTONE,
type Shadow, type Shadow,
@@ -48,6 +47,7 @@ import {PostControls} from '#/components/PostControls'
import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug' import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {SubtleHover} from '#/components/SubtleHover' import {SubtleHover} from '#/components/SubtleHover'
import {useAnalytics} from '#/analytics'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
import {PostFeedReason} from './PostFeedReason' import {PostFeedReason} from './PostFeedReason'
@@ -158,6 +158,7 @@ let FeedItemInner = ({
rootPost: AppBskyFeedDefs.PostView rootPost: AppBskyFeedDefs.PostView
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
}): React.ReactNode => { }): React.ReactNode => {
const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {openComposer} = useOpenComposer() const {openComposer} = useOpenComposer()
const pal = usePalette('default') const pal = usePalette('default')
@@ -198,7 +199,7 @@ let FeedItemInner = ({
feedContext, feedContext,
reqId, reqId,
}) })
logger.metric('post:clickthroughAuthor', { ax.metric('post:clickthroughAuthor', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext: 'FeedItem', logContext: 'FeedItem',
@@ -222,7 +223,7 @@ let FeedItemInner = ({
feedContext, feedContext,
reqId, reqId,
}) })
logger.metric('post:clickthroughEmbed', { ax.metric('post:clickthroughEmbed', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext: 'FeedItem', logContext: 'FeedItem',
@@ -237,7 +238,7 @@ let FeedItemInner = ({
feedContext, feedContext,
reqId, reqId,
}) })
logger.metric('post:clickthroughItem', { ax.metric('post:clickthroughItem', {
uri: post.uri, uri: post.uri,
authorDid: post.author.did, authorDid: post.author.did,
logContext: 'FeedItem', logContext: 'FeedItem',
+12 -14
View File
@@ -12,6 +12,7 @@ import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {useAnalytics} from '#/analytics'
import {List} from '../util/List' import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from './ProfileCard' import {ProfileCardWithFollowBtn} from './ProfileCard'
@@ -41,6 +42,7 @@ function keyExtractor(item: ActorDefs.ProfileViewBasic) {
export function ProfileFollowers({name}: {name: string}) { export function ProfileFollowers({name}: {name: string}) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const navigation = useNavigation() const navigation = useNavigation()
const initialNumToRender = useInitialNumToRender() const initialNumToRender = useInitialNumToRender()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -88,14 +90,14 @@ export function ProfileFollowers({name}: {name: string}) {
currentPageCount >= 3 && currentPageCount >= 3 &&
currentPageCount > paginationTrackingRef.current.page currentPageCount > paginationTrackingRef.current.page
) { ) {
logger.metric('profile:followers:paginate', { ax.metric('profile:followers:paginate', {
contextProfileDid: resolvedDid, contextProfileDid: resolvedDid,
itemCount: followers.length, itemCount: followers.length,
page: currentPageCount, page: currentPageCount,
}) })
} }
paginationTrackingRef.current.page = currentPageCount paginationTrackingRef.current.page = currentPageCount
}, [data?.pages?.length, resolvedDid, followers.length]) }, [ax, data?.pages?.length, resolvedDid, followers.length])
const onRefresh = React.useCallback(async () => { const onRefresh = React.useCallback(async () => {
setIsPTRing(true) setIsPTRing(true)
@@ -125,12 +127,12 @@ export function ProfileFollowers({name}: {name: string}) {
// track pageview // track pageview
React.useEffect(() => { React.useEffect(() => {
if (resolvedDid) { if (resolvedDid) {
logger.metric('profile:followers:view', { ax.metric('profile:followers:view', {
contextProfileDid: resolvedDid, contextProfileDid: resolvedDid,
isOwnProfile: isMe, isOwnProfile: isMe,
}) })
} }
}, [resolvedDid, isMe]) }, [ax, resolvedDid, isMe])
// track seen items // track seen items
const seenItemsRef = React.useRef<Set<string>>(new Set()) const seenItemsRef = React.useRef<Set<string>>(new Set())
@@ -147,17 +149,13 @@ export function ProfileFollowers({name}: {name: string}) {
if (position === 0) { if (position === 0) {
return return
} }
logger.metric( ax.metric('profileCard:seen', {
'profileCard:seen', profileDid: item.did,
{ position,
profileDid: item.did, ...(resolvedDid !== undefined && {contextProfileDid: resolvedDid}),
position, })
...(resolvedDid !== undefined && {contextProfileDid: resolvedDid}),
},
{statsig: false},
)
}, },
[followers, resolvedDid], [ax, followers, resolvedDid],
) )
if (followers.length < 1) { if (followers.length < 1) {
+12 -14
View File
@@ -14,6 +14,7 @@ import {useSession} from '#/state/session'
import {FindContactsBannerNUX} from '#/components/contacts/FindContactsBannerNUX' import {FindContactsBannerNUX} from '#/components/contacts/FindContactsBannerNUX'
import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {List} from '../util/List' import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from './ProfileCard' import {ProfileCardWithFollowBtn} from './ProfileCard'
@@ -44,6 +45,7 @@ function keyExtractor(item: ActorDefs.ProfileViewBasic) {
export function ProfileFollows({name}: {name: string}) { export function ProfileFollows({name}: {name: string}) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const initialNumToRender = useInitialNumToRender() const initialNumToRender = useInitialNumToRender()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@@ -100,14 +102,14 @@ export function ProfileFollows({name}: {name: string}) {
currentPageCount >= 3 && currentPageCount >= 3 &&
currentPageCount > paginationTrackingRef.current.page currentPageCount > paginationTrackingRef.current.page
) { ) {
logger.metric('profile:following:paginate', { ax.metric('profile:following:paginate', {
contextProfileDid: resolvedDid, contextProfileDid: resolvedDid,
itemCount: follows.length, itemCount: follows.length,
page: currentPageCount, page: currentPageCount,
}) })
} }
paginationTrackingRef.current.page = currentPageCount paginationTrackingRef.current.page = currentPageCount
}, [data?.pages?.length, resolvedDid, follows.length]) }, [ax, data?.pages?.length, resolvedDid, follows.length])
const onRefresh = React.useCallback(async () => { const onRefresh = React.useCallback(async () => {
setIsPTRing(true) setIsPTRing(true)
@@ -137,12 +139,12 @@ export function ProfileFollows({name}: {name: string}) {
// track pageview // track pageview
React.useEffect(() => { React.useEffect(() => {
if (resolvedDid) { if (resolvedDid) {
logger.metric('profile:following:view', { ax.metric('profile:following:view', {
contextProfileDid: resolvedDid, contextProfileDid: resolvedDid,
isOwnProfile: isMe, isOwnProfile: isMe,
}) })
} }
}, [resolvedDid, isMe]) }, [ax, resolvedDid, isMe])
// track seen items // track seen items
const seenItemsRef = React.useRef<Set<string>>(new Set()) const seenItemsRef = React.useRef<Set<string>>(new Set())
@@ -159,17 +161,13 @@ export function ProfileFollows({name}: {name: string}) {
if (position === 0) { if (position === 0) {
return return
} }
logger.metric( ax.metric('profileCard:seen', {
'profileCard:seen', profileDid: item.did,
{ position,
profileDid: item.did, ...(resolvedDid !== undefined && {contextProfileDid: resolvedDid}),
position, })
...(resolvedDid !== undefined && {contextProfileDid: resolvedDid}),
},
{statsig: false},
)
}, },
[follows, resolvedDid], [ax, follows, resolvedDid],
) )
if (follows.length < 1) { if (follows.length < 1) {
+13 -12
View File
@@ -11,7 +11,6 @@ import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {shareText, shareUrl} from '#/lib/sharing' import {shareText, shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
@@ -60,6 +59,7 @@ import * as Prompt from '#/components/Prompt'
import {useFullVerificationState} from '#/components/verification' import {useFullVerificationState} from '#/components/verification'
import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt'
import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {Dot} from '#/features/nuxs/components/Dot' import {Dot} from '#/features/nuxs/components/Dot'
import {Gradient} from '#/features/nuxs/components/Gradient' import {Gradient} from '#/features/nuxs/components/Gradient'
@@ -71,6 +71,7 @@ let ProfileMenu = ({
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
}): React.ReactNode => { }): React.ReactNode => {
const t = useTheme() const t = useTheme()
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
const {openModal} = useModalControls() const {openModal} = useModalControls()
@@ -121,7 +122,7 @@ let ProfileMenu = ({
}, [queryClient, profile.did]) }, [queryClient, profile.did])
const onPressAddToStarterPacks = React.useCallback(() => { const onPressAddToStarterPacks = React.useCallback(() => {
logger.metric('profile:addToStarterPack', {}) ax.metric('profile:addToStarterPack', {})
addToStarterPacksDialogControl.open() addToStarterPacksDialogControl.open()
}, [addToStarterPacksDialogControl]) }, [addToStarterPacksDialogControl])
@@ -147,7 +148,7 @@ let ProfileMenu = ({
Toast.show(_(msg({message: 'Account unmuted', context: 'toast'}))) Toast.show(_(msg({message: 'Account unmuted', context: 'toast'})))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to unmute account', {message: e}) ax.logger.error('Failed to unmute account', {message: e})
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
} }
} }
@@ -157,12 +158,12 @@ let ProfileMenu = ({
Toast.show(_(msg({message: 'Account muted', context: 'toast'}))) Toast.show(_(msg({message: 'Account muted', context: 'toast'})))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to mute account', {message: e}) ax.logger.error('Failed to mute account', {message: e})
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
} }
} }
} }
}, [profile.viewer?.muted, queueUnmute, _, queueMute]) }, [ax, profile.viewer?.muted, queueUnmute, _, queueMute])
const blockAccount = React.useCallback(async () => { const blockAccount = React.useCallback(async () => {
if (profile.viewer?.blocking) { if (profile.viewer?.blocking) {
@@ -171,7 +172,7 @@ let ProfileMenu = ({
Toast.show(_(msg({message: 'Account unblocked', context: 'toast'}))) Toast.show(_(msg({message: 'Account unblocked', context: 'toast'})))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to unblock account', {message: e}) ax.logger.error('Failed to unblock account', {message: e})
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
} }
} }
@@ -181,12 +182,12 @@ let ProfileMenu = ({
Toast.show(_(msg({message: 'Account blocked', context: 'toast'}))) Toast.show(_(msg({message: 'Account blocked', context: 'toast'})))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to block account', {message: e}) ax.logger.error('Failed to block account', {message: e})
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
} }
} }
} }
}, [profile.viewer?.blocking, _, queueUnblock, queueBlock]) }, [ax, profile.viewer?.blocking, _, queueUnblock, queueBlock])
const onPressFollowAccount = React.useCallback(async () => { const onPressFollowAccount = React.useCallback(async () => {
try { try {
@@ -194,11 +195,11 @@ let ProfileMenu = ({
Toast.show(_(msg({message: 'Account followed', context: 'toast'}))) Toast.show(_(msg({message: 'Account followed', context: 'toast'})))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to follow account', {message: e}) ax.logger.error('Failed to follow account', {message: e})
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
} }
} }
}, [_, queueFollow]) }, [_, ax, queueFollow])
const onPressUnfollowAccount = React.useCallback(async () => { const onPressUnfollowAccount = React.useCallback(async () => {
try { try {
@@ -206,11 +207,11 @@ let ProfileMenu = ({
Toast.show(_(msg({message: 'Account unfollowed', context: 'toast'}))) Toast.show(_(msg({message: 'Account unfollowed', context: 'toast'})))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to unfollow account', {message: e}) ax.logger.error('Failed to unfollow account', {message: e})
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
} }
} }
}, [_, queueUnfollow]) }, [_, ax, queueUnfollow])
const onPressReportAccount = React.useCallback(() => { const onPressReportAccount = React.useCallback(() => {
reportDialogControl.open() reportDialogControl.open()
+3 -5
View File
@@ -52,6 +52,7 @@ import {LiveStatusDialog} from '#/components/live/LiveStatusDialog'
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {useAnalytics} from '#/analytics'
import {IS_ANDROID, IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' import {IS_ANDROID, IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
@@ -530,6 +531,7 @@ let PreviewableUserAvatar = ({
live, live,
...props ...props
}: PreviewableUserAvatarProps): React.ReactNode => { }: PreviewableUserAvatarProps): React.ReactNode => {
const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const status = useActorStatus(profile) const status = useActorStatus(profile)
@@ -543,11 +545,7 @@ let PreviewableUserAvatar = ({
const onOpenLiveStatus = useCallback(() => { const onOpenLiveStatus = useCallback(() => {
playHaptic('Light') playHaptic('Light')
logger.metric( ax.metric('live:card:open', {subject: profile.did, from: 'post'})
'live:card:open',
{subject: profile.did, from: 'post'},
{statsig: true},
)
liveControl.open() liveControl.open()
}, [liveControl, playHaptic, profile.did]) }, [liveControl, playHaptic, profile.did])
+6 -9
View File
@@ -5,7 +5,6 @@ import {useNavigation, useNavigationState} from '@react-navigation/native'
import {getCurrentRoute} from '#/lib/routes/helpers' import {getCurrentRoute} from '#/lib/routes/helpers'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {logger} from '#/logger'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import { import {
type SavedFeedSourceInfo, type SavedFeedSourceInfo,
@@ -19,10 +18,12 @@ import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/compon
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {Link} from '#/components/Link' import {Link} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
export function DesktopFeeds() { export function DesktopFeeds() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos() const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos()
const selectedFeed = useSelectedFeed() const selectedFeed = useSelectedFeed()
const setSelectedFeed = useSetSelectedFeed() const setSelectedFeed = useSetSelectedFeed()
@@ -86,14 +87,10 @@ export function DesktopFeeds() {
feedInfo={feedInfo} feedInfo={feedInfo}
current={current} current={current}
onPress={() => { onPress={() => {
logger.metric( ax.metric('desktopFeeds:feed:click', {
'desktopFeeds:feed:click', feedUri: feedInfo.uri,
{ feedDescriptor: feed,
feedUri: feedInfo.uri, })
feedDescriptor: feed,
},
{statsig: false},
)
setSelectedFeed(feed) setSelectedFeed(feed)
navigation.navigate('Home') navigation.navigate('Home')
if (route.name === 'Home' && feed === selectedFeed) { if (route.name === 'Home' && feed === selectedFeed) {
@@ -2,7 +2,6 @@ import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import { import {
useTrendingSettings, useTrendingSettings,
useTrendingSettingsApi, useTrendingSettingsApi,
@@ -16,6 +15,7 @@ import {Trending3_Stroke2_Corner1_Rounded as TrendingIcon} from '#/components/ic
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {TrendingTopicLink} from '#/components/TrendingTopics' import {TrendingTopicLink} from '#/components/TrendingTopics'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
const TRENDING_LIMIT = 5 const TRENDING_LIMIT = 5
@@ -28,13 +28,14 @@ export function SidebarTrendingTopics() {
function Inner() { function Inner() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics()
const trendingPrompt = Prompt.usePromptControl() const trendingPrompt = Prompt.usePromptControl()
const {setTrendingDisabled} = useTrendingSettingsApi() const {setTrendingDisabled} = useTrendingSettingsApi()
const {data: trending, error, isLoading} = useTrendingTopics() const {data: trending, error, isLoading} = useTrendingTopics()
const noTopics = !isLoading && !error && !trending?.topics?.length const noTopics = !isLoading && !error && !trending?.topics?.length
const onConfirmHide = () => { const onConfirmHide = () => {
logger.metric('trendingTopics:hide', {context: 'sidebar'}) ax.metric('trendingTopics:hide', {context: 'sidebar'})
setTrendingDisabled(true) setTrendingDisabled(true)
} }
@@ -90,7 +91,7 @@ function Inner() {
topic={topic} topic={topic}
style={[a.self_start]} style={[a.self_start]}
onPress={() => { onPress={() => {
logger.metric('trendingTopic:click', {context: 'sidebar'}) ax.metric('trendingTopic:click', {context: 'sidebar'})
}}> }}>
{({hovered}) => ( {({hovered}) => (
<View style={[a.flex_row, a.align_center, a.gap_xs]}> <View style={[a.flex_row, a.align_center, a.gap_xs]}>