diff --git a/package.json b/package.json index a5be4a9be6..dd0d84d9b3 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.13.21", + "@atproto/api": "^0.13.27", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", "@emoji-mart/react": "^1.1.1", diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index ec224eeae0..fd6eb30c68 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -196,6 +196,7 @@ export function SuggestedFollowsProfile({did}: {did: string}) { @@ -222,10 +223,12 @@ export function ProfileGrid({ isSuggestionsLoading, error, profiles, + recId, viewContext = 'feed', }: { isSuggestionsLoading: boolean profiles: AppBskyActorDefs.ProfileViewDetailed[] + recId?: number error: Error | null viewContext: 'profile' | 'feed' }) { @@ -249,12 +252,20 @@ export function ProfileGrid({ )) ) : error || !profiles.length ? null : ( <> - {profiles.slice(0, maxLength).map(profile => ( + {profiles.slice(0, maxLength).map((profile, index) => ( { logEvent('feed:interstitial:profileCard:press', {}) + logEvent('suggestedUser:press', { + logContext: + viewContext === 'feed' + ? 'InterstitialDiscover' + : 'InterstitialProfile', + recId, + position: index, + }) }} style={[ a.flex_1, @@ -282,6 +293,17 @@ export function ProfileGrid({ logContext="FeedInterstitial" shape="round" colorInverted + onFollow={() => { + logEvent('suggestedUser:follow', { + logContext: + viewContext === 'feed' + ? 'InterstitialDiscover' + : 'InterstitialProfile', + location: 'Card', + recId, + position: index, + }) + }} /> diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 7bec14b9cc..78d86ab362 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -286,6 +286,7 @@ export type FollowButtonProps = { logContext: LogEvents['profile:follow']['logContext'] & LogEvents['profile:unfollow']['logContext'] colorInverted?: boolean + onFollow?: () => void } & Partial export function FollowButton(props: FollowButtonProps) { @@ -299,6 +300,7 @@ export function FollowButtonInner({ moderationOpts, logContext, onPress: onPressProp, + onFollow, colorInverted, ...rest }: FollowButtonProps) { @@ -325,6 +327,7 @@ export function FollowButtonInner({ ), ) onPressProp?.(e) + onFollow?.() } catch (err: any) { if (err?.name !== 'AbortError') { Toast.show(_(msg`An issue occurred, please try again.`), 'xmark') diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 566edf69db..1c049ea818 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -63,6 +63,9 @@ export function IS_PROD_SERVICE(url?: string) { export const PROD_DEFAULT_FEED = (rkey: string) => `at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/${rkey}` +export const STAGING_DEFAULT_FEED = (rkey: string) => + `at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/${rkey}` + export const POST_IMG_MAX = { width: 2000, height: 2000, diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 19bf06ba98..2888484aa9 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -164,6 +164,26 @@ export type LogEvents = { | 'ProfileHeaderSuggestedFollows' | 'PostOnboardingFindFollows' } + 'suggestedUser:follow': { + logContext: + | 'Explore' + | 'InterstitialDiscover' + | 'InterstitialProfile' + | 'Profile' + location: 'Card' | 'Profile' + recId?: number + position: number + } + 'suggestedUser:press': { + logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile' + recId?: number + position: number + } + 'suggestedUser:seen': { + logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile' + recId?: number + position: number + } 'profile:unfollow': { logContext: | 'RecommendedFollowsItem' diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index 02f98ad145..9c7358e7af 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -3,7 +3,7 @@ import {AppState, AppStateStatus} from 'react-native' import {AppBskyFeedDefs} from '@atproto/api' import throttle from 'lodash.throttle' -import {PROD_DEFAULT_FEED} from '#/lib/constants' +import {PROD_DEFAULT_FEED, STAGING_DEFAULT_FEED} from '#/lib/constants' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {FeedDescriptor, FeedPostSliceItem} from '#/state/queries/post-feed' @@ -54,7 +54,7 @@ export function useFeedFeedback(feed: FeedDescriptor, hasSession: boolean) { encoding: 'application/json', headers: { // TODO when we start sending to other feeds, we need to grab their DID -prf - 'atproto-proxy': 'did:web:discover.bsky.app#bsky_fg', + 'atproto-proxy': 'did:web:algo.pop2.bsky.app#bsky_fg', }, }, ) @@ -155,7 +155,10 @@ export function useFeedFeedbackContext() { // place, we're hardcoding it to the discover feed. // -prf function isDiscoverFeed(feed: FeedDescriptor) { - return feed === `feedgen|${PROD_DEFAULT_FEED('whats-hot')}` + return ( + feed === `feedgen|${PROD_DEFAULT_FEED('whats-hot')}` || + feed === `feedgen|${STAGING_DEFAULT_FEED('thevids')}` + ) } function toString(interaction: AppBskyFeedDefs.Interaction): string { diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 22033c0a8c..0a2343150c 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -120,7 +120,7 @@ export function useSuggestedFollowsByActorQuery({ const suggestions = res.data.isFallback ? [] : res.data.suggestions.filter(profile => !profile.viewer?.following) - return {suggestions} + return {suggestions, recId: res.data.recId} }, enabled, }) diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx index c2d76316e6..ff58dc9455 100644 --- a/src/view/com/profile/FollowButton.tsx +++ b/src/view/com/profile/FollowButton.tsx @@ -14,12 +14,14 @@ export function FollowButton({ profile, labelStyle, logContext, + onFollow, }: { unfollowedType?: ButtonType followedType?: ButtonType profile: Shadow labelStyle?: StyleProp logContext: 'ProfileCard' | 'StarterPackProfilesList' + onFollow?: () => void }) { const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( profile, @@ -30,6 +32,7 @@ export function FollowButton({ const onPressFollow = async () => { try { await queueFollow() + onFollow?.() } catch (e: any) { if (e?.name !== 'AbortError') { Toast.show(_(msg`An issue occurred, please try again.`), 'xmark') diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index eab8611dd4..f710d7b4e7 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -184,6 +184,7 @@ export function ProfileCardWithFollowBtn({ noBg, noBorder, onPress, + onFollow, logContext = 'ProfileCard', showKnownFollowers, }: { @@ -191,6 +192,7 @@ export function ProfileCardWithFollowBtn({ noBg?: boolean noBorder?: boolean onPress?: () => void + onFollow?: () => void logContext?: 'ProfileCard' | 'StarterPackProfilesList' showKnownFollowers?: boolean }) { @@ -206,7 +208,11 @@ export function ProfileCardWithFollowBtn({ isMe ? undefined : profileShadow => ( - + ) } onPress={onPress} diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index 378ea59a4d..324339ebf1 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -10,6 +10,7 @@ import { import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {logEvent} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' @@ -253,6 +254,7 @@ type ExploreScreenItems = type: 'profile' key: string profile: AppBskyActorDefs.ProfileView + recId?: number } | { type: 'feed' @@ -370,6 +372,7 @@ export function Explore() { type: 'profile', key: actor.did, profile: actor, + recId: page.recId, }) } } @@ -498,7 +501,7 @@ export function Explore() { ]) const renderItem = React.useCallback( - ({item}: {item: ExploreScreenItems}) => { + ({item, index}: {item: ExploreScreenItems; index: number}) => { switch (item.type) { case 'header': { return ( @@ -524,6 +527,21 @@ export function Explore() { noBg noBorder showKnownFollowers + onPress={() => { + logEvent('suggestedUser:press', { + logContext: 'Explore', + recId: item.recId, + position: index, + }) + }} + onFollow={() => { + logEvent('suggestedUser:follow', { + logContext: 'Explore', + location: 'Card', + recId: item.recId, + position: index, + }) + }} /> ) diff --git a/yarn.lock b/yarn.lock index da037040ce..c6939d7fb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -72,15 +72,15 @@ tlds "^1.234.0" zod "^3.23.8" -"@atproto/api@^0.13.21": - version "0.13.21" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.21.tgz#8ee27a07e5a024b5bf32408d9bd623dd598ad1cc" - integrity sha512-iOxSj2YS3Fx9IPz1NivKrSsdYPNbBgpnUH7+WhKYAMvDFDUe2PZe7taau8wsUjJAu/H3S0Mk2TDh5e/7tCRwHA== +"@atproto/api@^0.13.27": + version "0.13.27" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.27.tgz#54d1172fbc4415d41089d2715a657052b26b60bc" + integrity sha512-IaZezARJSoFBxFvutGa+2hT0NiA+db6NpKkla2bRyv/SPinYViuqD5kaaPzc732yeE1uwUKQc4wwwlctYamFBQ== dependencies: - "@atproto/common-web" "^0.3.1" - "@atproto/lexicon" "^0.4.4" + "@atproto/common-web" "^0.3.2" + "@atproto/lexicon" "^0.4.5" "@atproto/syntax" "^0.3.1" - "@atproto/xrpc" "^0.6.5" + "@atproto/xrpc" "^0.6.6" await-lock "^2.2.2" multiformats "^9.9.0" tlds "^1.234.0" @@ -169,6 +169,16 @@ uint8arrays "3.0.0" zod "^3.23.8" +"@atproto/common-web@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.2.tgz#4cf78ad4d24fed801882f3d35afc39bceccdff51" + integrity sha512-Vx0JtL1/CssJbFAb0UOdvTrkbUautsDfHNOXNTcX2vyPIxH9xOameSqLLunM1hZnOQbJwyjmQCt6TV+bhnanDg== + dependencies: + graphemer "^1.4.0" + multiformats "^9.9.0" + uint8arrays "3.0.0" + zod "^3.23.8" + "@atproto/common@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210" @@ -283,6 +293,17 @@ multiformats "^9.9.0" zod "^3.23.8" +"@atproto/lexicon@^0.4.5": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.5.tgz#4fcf3731193c674286e9e8d677bbab5dd530b817" + integrity sha512-fljWqMGKn+XWtTprBcS3F1hGBREnQYh6qYHv2sjENucc7REms1gtmZXSerB9N6pVeHVNOnXiILdukeAcic5OEw== + dependencies: + "@atproto/common-web" "^0.3.2" + "@atproto/syntax" "^0.3.1" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + zod "^3.23.8" + "@atproto/oauth-provider@^0.2.10": version "0.2.10" resolved "https://registry.yarnpkg.com/@atproto/oauth-provider/-/oauth-provider-0.2.10.tgz#f9820d7f82c33d3b74e81a75873f50e1e654b901" @@ -452,6 +473,14 @@ "@atproto/lexicon" "^0.4.4" zod "^3.23.8" +"@atproto/xrpc@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.6.tgz#28f58270ef4a8056f7f718bd52512e74bcd3702f" + integrity sha512-umXEYVMo9/pyIBoKmIAIi64RXDW9tSXY+wqztlQ6I2GZtjLfNZqmAWU+wADk3SxUe54mvjxxGyA4TtyGtDMfhA== + dependencies: + "@atproto/lexicon" "^0.4.5" + zod "^3.23.8" + "@aws-crypto/crc32@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa"