From 5d6647a7a284561ef3a91341c8352a1099327d49 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 5 Feb 2026 11:31:31 -0800 Subject: [PATCH 1/2] Add explore analytics --- packages/common/src/models/Analytics.ts | 42 ++++++++++ .../components/FeaturedArtistCoinTracks.tsx | 5 +- .../components/FeaturedPlaylists.tsx | 5 +- .../components/ForYouTracks.tsx | 5 +- .../explore-screen/components/MoodsGrid.tsx | 80 +++++++++++-------- .../components/QuickSearchGrid.tsx | 42 ++++++---- .../components/RecentlyPlayed.tsx | 5 +- .../hooks/useExploreSectionTracking.ts | 26 ++++++ .../desktop/ActiveDiscussionsSection.tsx | 4 +- .../desktop/ArtistCoinTracksSection.tsx | 4 +- .../desktop/ArtistSpotlightSection.tsx | 4 +- .../components/desktop/BestSellingSection.tsx | 4 +- .../components/desktop/CollectionArtCard.tsx | 36 ++++++++- .../desktop/DownloadsAvailableSection.tsx | 4 +- .../desktop/FeaturedPlaylistsSection.tsx | 4 +- .../desktop/FeaturedRemixContestsSection.tsx | 4 +- .../desktop/FeelingLuckySection.tsx | 4 +- .../desktop/LabelSpotlightSection.tsx | 4 +- .../components/desktop/MoodGrid.tsx | 16 +++- .../components/desktop/MostSharedSection.tsx | 4 +- .../components/desktop/QuickSearchGrid.tsx | 28 ++++++- .../desktop/RecentPremiumTracksSection.tsx | 4 +- .../desktop/RecentSearchesSection.tsx | 4 +- .../desktop/RecentlyPlayedSection.tsx | 4 +- .../desktop/RecommendedTracksSection.tsx | 4 +- .../components/desktop/TrackArtCard.tsx | 26 +++++- .../desktop/TrendingPlaylistsSection.tsx | 4 +- .../UndergroundTrendingTracksSection.tsx | 6 +- .../components/desktop/UserArtCard.tsx | 26 +++++- .../desktop/useExploreSectionTracking.ts | 29 +++++++ 30 files changed, 337 insertions(+), 100 deletions(-) create mode 100644 packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts create mode 100644 packages/web/src/pages/search-explore-page/components/desktop/useExploreSectionTracking.ts diff --git a/packages/common/src/models/Analytics.ts b/packages/common/src/models/Analytics.ts index 69dd1b6e1e8..cae82b2e5d9 100644 --- a/packages/common/src/models/Analytics.ts +++ b/packages/common/src/models/Analytics.ts @@ -308,6 +308,10 @@ export enum Name { SEARCH_RESULT_SELECT = 'Search: Result Select', SEARCH_TAB_CLICK = 'Search: Tab Click', + // Explore + EXPLORE_SECTION_VIEW = 'Explore: Section View', + EXPLORE_SECTION_CLICK = 'Explore: Section Click', + // Errors ERROR_PAGE = 'Error Page', NOT_FOUND_PAGE = 'Not Found Page', @@ -1555,6 +1559,42 @@ type SearchResultSelect = { kind: 'track' | 'profile' | 'playlist' | 'album' } +// Explore +export type ExploreSectionName = + | 'Recommended Tracks' + | 'Artist Coin Tracks' + | 'Recently Played' + | 'Quick Search' + | 'Featured Playlists' + | 'Featured Remix Contests' + | 'Underground Trending Tracks' + | 'Artist Spotlight' + | 'Label Spotlight' + | 'Active Discussions' + | 'Downloads Available' + | 'Mood Grid' + | 'Trending Playlists' + | 'Most Shared' + | 'Best Selling' + | 'Recent Premium Tracks' + | 'Feeling Lucky' + | 'Recent Searches' + +type ExploreSectionView = { + eventName: Name.EXPLORE_SECTION_VIEW + section: ExploreSectionName + source: 'web' | 'mobile' +} + +type ExploreSectionClick = { + eventName: Name.EXPLORE_SECTION_CLICK + section: ExploreSectionName + source: 'web' | 'mobile' + id?: ID + kind?: 'track' | 'profile' | 'playlist' | 'album' | 'mood' | 'preset' + link?: string +} + type ListenGated = { eventName: Name.LISTEN_GATED trackId: string @@ -3130,6 +3170,8 @@ export type AllTrackingEvents = | SearchTag | SearchMoreResults | SearchResultSelect + | ExploreSectionView + | ExploreSectionClick | ListenGated | ErrorPage | NotFoundPage diff --git a/packages/mobile/src/screens/explore-screen/components/FeaturedArtistCoinTracks.tsx b/packages/mobile/src/screens/explore-screen/components/FeaturedArtistCoinTracks.tsx index 1bcc9e4d148..7e71979c9a7 100644 --- a/packages/mobile/src/screens/explore-screen/components/FeaturedArtistCoinTracks.tsx +++ b/packages/mobile/src/screens/explore-screen/components/FeaturedArtistCoinTracks.tsx @@ -4,13 +4,14 @@ import { useExploreContent } from '@audius/common/api' import { exploreMessages as messages } from '@audius/common/messages' import { QueueSource } from '@audius/common/store' -import { useDeferredElement } from '../../../hooks/useDeferredElement' +import { useExploreSectionTracking } from '../hooks/useExploreSectionTracking' import { ExploreSection } from './ExploreSection' import { TrackTileCarousel } from './TrackTileCarousel' export const FeaturedArtistCoinTracks = () => { - const { InViewWrapper, inView } = useDeferredElement() + const { InViewWrapper, inView } = + useExploreSectionTracking('Artist Coin Tracks') const { data, isPending } = useExploreContent({ enabled: inView }) return ( diff --git a/packages/mobile/src/screens/explore-screen/components/FeaturedPlaylists.tsx b/packages/mobile/src/screens/explore-screen/components/FeaturedPlaylists.tsx index 9f1c0f352f1..0bf456d6856 100644 --- a/packages/mobile/src/screens/explore-screen/components/FeaturedPlaylists.tsx +++ b/packages/mobile/src/screens/explore-screen/components/FeaturedPlaylists.tsx @@ -6,13 +6,14 @@ import { exploreMessages as messages } from '@audius/common/messages' import { useTheme } from '@audius/harmony-native' import { CollectionList } from 'app/components/collection-list' -import { useDeferredElement } from '../../../hooks/useDeferredElement' +import { useExploreSectionTracking } from '../hooks/useExploreSectionTracking' import { ExploreSection } from './ExploreSection' export const FeaturedPlaylists = () => { const { spacing } = useTheme() - const { InViewWrapper, inView } = useDeferredElement() + const { InViewWrapper, inView } = + useExploreSectionTracking('Featured Playlists') const { data: exploreContent } = useExploreContent({ enabled: inView }) const { data: playlists } = useCollections( diff --git a/packages/mobile/src/screens/explore-screen/components/ForYouTracks.tsx b/packages/mobile/src/screens/explore-screen/components/ForYouTracks.tsx index 10d021a6096..9a34e5f2398 100644 --- a/packages/mobile/src/screens/explore-screen/components/ForYouTracks.tsx +++ b/packages/mobile/src/screens/explore-screen/components/ForYouTracks.tsx @@ -5,13 +5,14 @@ import { exploreMessages as messages } from '@audius/common/messages' import { QueueSource } from '@audius/common/store' import { full } from '@audius/sdk' -import { useDeferredElement } from 'app/hooks/useDeferredElement' +import { useExploreSectionTracking } from '../hooks/useExploreSectionTracking' import { ExploreSection } from './ExploreSection' import { TrackTileCarousel } from './TrackTileCarousel' export const ForYouTracks = () => { - const { inView, InViewWrapper } = useDeferredElement() + const { inView, InViewWrapper } = + useExploreSectionTracking('Recommended Tracks') const { data: recommendedTracks, isPending } = useRecommendedTracks( { pageSize: 10, timeRange: full.GetRecommendedTracksTimeEnum.Week }, { enabled: inView } diff --git a/packages/mobile/src/screens/explore-screen/components/MoodsGrid.tsx b/packages/mobile/src/screens/explore-screen/components/MoodsGrid.tsx index f72bfb3c8d6..1c9952cf513 100644 --- a/packages/mobile/src/screens/explore-screen/components/MoodsGrid.tsx +++ b/packages/mobile/src/screens/explore-screen/components/MoodsGrid.tsx @@ -1,7 +1,9 @@ import React, { useMemo, useCallback } from 'react' import { labelByCategory } from '@audius/common/api' +import { useAnalytics } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' +import { Name } from '@audius/common/models' import type { Mood } from '@audius/sdk' import { MOODS } from 'pages/search-page/moods' import type { MoodInfo } from 'pages/search-page/types' @@ -14,6 +16,7 @@ import { useSearchCategory, useSearchFilters } from '../../search-screen/searchState.tsx' +import { useExploreSectionTracking } from '../hooks/useExploreSectionTracking' import { ExploreSection } from './ExploreSection.tsx' @@ -22,9 +25,11 @@ interface MoodsGridProps { } export const MoodsGrid = ({ isLoading: externalLoading }: MoodsGridProps) => { + const { InViewWrapper } = useExploreSectionTracking('Mood Grid') const { spacing } = useTheme() const [category, setCategory] = useSearchCategory() const [, setFilters] = useSearchFilters() + const { trackEvent } = useAnalytics() const moodEntries = useMemo( () => Object.entries(MOODS) as [string, MoodInfo][], @@ -33,50 +38,59 @@ export const MoodsGrid = ({ isLoading: externalLoading }: MoodsGridProps) => { const handleMoodPress = useCallback( (moodLabel: Mood) => { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: 'Mood Grid', + source: 'mobile', + kind: 'mood', + link: moodLabel + }) if (category === 'all') { setCategory('tracks') } setFilters({ mood: moodLabel }) }, - [setFilters, setCategory, category] + [setFilters, setCategory, category, trackEvent] ) if (externalLoading) { return null } return ( - - - {moodEntries.sort().map(([_, moodInfo]) => ( - { - handleMoodPress(moodInfo.label) - }} - > - + + + {moodEntries.sort().map(([_, moodInfo]) => ( + { + handleMoodPress(moodInfo.label) }} - /> - - {moodInfo.label} - - - ))} - - + > + + + {moodInfo.label} + + + ))} + + + ) } diff --git a/packages/mobile/src/screens/explore-screen/components/QuickSearchGrid.tsx b/packages/mobile/src/screens/explore-screen/components/QuickSearchGrid.tsx index 5b487eebb08..fc790b53ff2 100644 --- a/packages/mobile/src/screens/explore-screen/components/QuickSearchGrid.tsx +++ b/packages/mobile/src/screens/explore-screen/components/QuickSearchGrid.tsx @@ -1,6 +1,8 @@ import React, { useCallback, useMemo } from 'react' +import { useAnalytics } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' +import { Name } from '@audius/common/models' import { QUICK_SEARCH_PRESETS, type QuickSearchPreset @@ -15,6 +17,7 @@ import { useSearchCategory, useSearchFilters } from '../../search-screen/searchState.tsx' +import { useExploreSectionTracking } from '../hooks/useExploreSectionTracking' import { ExploreSection } from './ExploreSection.tsx' @@ -102,11 +105,20 @@ const QuickSearchPresetButton = ({ } export const QuickSearchGrid = () => { + const { InViewWrapper } = useExploreSectionTracking('Quick Search') const [, setCategory] = useSearchCategory() const [, setFilters] = useSearchFilters() + const { trackEvent } = useAnalytics() const handlePressPreset = useCallback( (preset: QuickSearchPreset) => { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: 'Quick Search', + source: 'mobile', + kind: 'preset', + link: `${preset.genre || ''}-${preset.mood || ''}-${preset.key || ''}` + }) // TODO: Support tabs setCategory('tracks') setFilters({ @@ -117,22 +129,24 @@ export const QuickSearchGrid = () => { key: preset.key }) }, - [setCategory, setFilters] + [setCategory, setFilters, trackEvent] ) return ( - - - {QUICK_SEARCH_PRESETS.map((preset, idx) => ( - { - handlePressPreset(preset) - }} - /> - ))} - - + + + + {QUICK_SEARCH_PRESETS.map((preset, idx) => ( + { + handlePressPreset(preset) + }} + /> + ))} + + + ) } diff --git a/packages/mobile/src/screens/explore-screen/components/RecentlyPlayed.tsx b/packages/mobile/src/screens/explore-screen/components/RecentlyPlayed.tsx index 03042a85cec..18aa1c2826b 100644 --- a/packages/mobile/src/screens/explore-screen/components/RecentlyPlayed.tsx +++ b/packages/mobile/src/screens/explore-screen/components/RecentlyPlayed.tsx @@ -5,13 +5,14 @@ import { exploreMessages as messages } from '@audius/common/messages' import { useTheme } from '@audius/harmony-native' import { TrackCardList } from 'app/components/track-card-list' -import { useDeferredElement } from 'app/hooks/useDeferredElement' + +import { useExploreSectionTracking } from '../hooks/useExploreSectionTracking' import { ExploreSection } from './ExploreSection' export const RecentlyPlayedTracks = () => { const { spacing } = useTheme() - const { InViewWrapper, inView } = useDeferredElement() + const { InViewWrapper, inView } = useExploreSectionTracking('Recently Played') const { data: recentlyPlayedTracks } = useRecentlyPlayedTracks( { pageSize: 10 }, { enabled: inView } diff --git a/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts b/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts new file mode 100644 index 00000000000..70a4a10434e --- /dev/null +++ b/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts @@ -0,0 +1,26 @@ +import { useEffect } from 'react' + +import { useAnalytics } from '@audius/common/hooks' +import { ExploreSectionName, Name } from '@audius/common/models' + +import { useDeferredElement } from 'app/hooks/useDeferredElement' + +/** + * Hook to track explore section impressions when they come into view on mobile + */ +export const useExploreSectionTracking = (sectionName: ExploreSectionName) => { + const { inView, InViewWrapper } = useDeferredElement() + const { trackEvent } = useAnalytics() + + useEffect(() => { + if (inView) { + trackEvent({ + eventName: Name.EXPLORE_SECTION_VIEW, + section: sectionName, + source: 'mobile' + }) + } + }, [inView, sectionName, trackEvent]) + + return { inView, InViewWrapper } +} diff --git a/packages/web/src/pages/search-explore-page/components/desktop/ActiveDiscussionsSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/ActiveDiscussionsSection.tsx index ba57b96ca0d..98dda5294af 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/ActiveDiscussionsSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/ActiveDiscussionsSection.tsx @@ -3,10 +3,10 @@ import { exploreMessages as messages } from '@audius/common/messages' import { Carousel } from './Carousel' import { TilePairs, TileSkeletons } from './TileHelpers' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const ActiveDiscussionsSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Active Discussions') const { data, isLoading, isError, isSuccess } = useRecentlyCommentedTracks( { pageSize: 10 }, diff --git a/packages/web/src/pages/search-explore-page/components/desktop/ArtistCoinTracksSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/ArtistCoinTracksSection.tsx index a56279c7d73..0ed267abeb0 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/ArtistCoinTracksSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/ArtistCoinTracksSection.tsx @@ -3,10 +3,10 @@ import { exploreMessages as messages } from '@audius/common/messages' import { Carousel } from './Carousel' import { TilePairs, TileSkeletons } from './TileHelpers' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const ArtistCoinTracksSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Artist Coin Tracks') const { data, isLoading, isError, isSuccess } = useExploreContent({ enabled: inView diff --git a/packages/web/src/pages/search-explore-page/components/desktop/ArtistSpotlightSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/ArtistSpotlightSection.tsx index efc79fd6c25..c8491ba69c9 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/ArtistSpotlightSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/ArtistSpotlightSection.tsx @@ -5,10 +5,10 @@ import { UserCard, UserCardSkeleton } from 'components/user-card' import { useIsMobile } from 'hooks/useIsMobile' import { Carousel } from './Carousel' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const ArtistSpotlightSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Artist Spotlight') const { data, isLoading, isError, isSuccess } = useExploreContent({ enabled: inView }) diff --git a/packages/web/src/pages/search-explore-page/components/desktop/BestSellingSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/BestSellingSection.tsx index c8b95e18ad7..ef0b7afd055 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/BestSellingSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/BestSellingSection.tsx @@ -8,10 +8,10 @@ import { TrackCardSkeleton } from 'components/track/TrackCard' import { useSearchCategory } from 'pages/search-page/hooks' import { Carousel } from './Carousel' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const BestSellingSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Best Selling') const [category] = useSearchCategory() diff --git a/packages/web/src/pages/search-explore-page/components/desktop/CollectionArtCard.tsx b/packages/web/src/pages/search-explore-page/components/desktop/CollectionArtCard.tsx index b1f207b8b47..7e1e7cf042c 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/CollectionArtCard.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/CollectionArtCard.tsx @@ -1,7 +1,13 @@ import { useCallback, useState } from 'react' import { useCollection, useUser } from '@audius/common/api' -import { ID, SquareSizes } from '@audius/common/models' +import { useAnalytics } from '@audius/common/hooks' +import { + ID, + SquareSizes, + ExploreSectionName, + Name +} from '@audius/common/models' import { Flex } from '@audius/harmony' import { useNavigate } from 'react-router' @@ -11,23 +17,47 @@ import { UserLink } from 'components/link/UserLink' import PerspectiveCard from 'components/perspective-card/PerspectiveCard' import { FavoriteStats } from 'components/stats/FavoriteStats' import { RepostStats } from 'components/stats/RepostStats' +import { useIsMobile } from 'hooks/useIsMobile' import { UserListEntityType } from 'store/application/ui/userListModal/types' type CollectionArtCardProps = { id: ID + sectionName?: ExploreSectionName } const ARTWORK_SIZE = 240 -export const CollectionArtCard = ({ id }: CollectionArtCardProps) => { +export const CollectionArtCard = ({ + id, + sectionName +}: CollectionArtCardProps) => { const [isPerspectiveDisabled] = useState(false) const navigate = useNavigate() + const { trackEvent } = useAnalytics() + const isMobile = useIsMobile() const { data: partialCollection } = useCollection(id) const goToPlaylist = useCallback(() => { if (!partialCollection?.permalink) return + if (sectionName) { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: sectionName, + source: isMobile ? 'mobile' : 'web', + id, + kind: 'playlist', + link: partialCollection.permalink + }) + } navigate(partialCollection.permalink) - }, [navigate, partialCollection?.permalink]) + }, [ + navigate, + partialCollection?.permalink, + sectionName, + id, + trackEvent, + isMobile + ]) const { data: user } = useUser(partialCollection?.playlist_owner_id) diff --git a/packages/web/src/pages/search-explore-page/components/desktop/DownloadsAvailableSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/DownloadsAvailableSection.tsx index 55c7169d37a..dbffc78e312 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/DownloadsAvailableSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/DownloadsAvailableSection.tsx @@ -6,10 +6,10 @@ import { QueueSource } from '@audius/common/store' import { Carousel } from './Carousel' import { TilePairs, TileSkeletons } from './TileHelpers' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const DownloadsAvailableSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Downloads Available') const { data: lineupData, isLoading, diff --git a/packages/web/src/pages/search-explore-page/components/desktop/FeaturedPlaylistsSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/FeaturedPlaylistsSection.tsx index d6dd668b7f7..2677d92060b 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/FeaturedPlaylistsSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/FeaturedPlaylistsSection.tsx @@ -5,10 +5,10 @@ import { CollectionCard, CollectionCardSkeleton } from 'components/collection' import { useIsMobile } from 'hooks/useIsMobile' import { Carousel } from './Carousel' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const FeaturedPlaylistsSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Featured Playlists') const { data, isLoading, isError, isSuccess } = useExploreContent({ enabled: inView diff --git a/packages/web/src/pages/search-explore-page/components/desktop/FeaturedRemixContestsSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/FeaturedRemixContestsSection.tsx index 2edcff8bbfc..2eb0f9cbff1 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/FeaturedRemixContestsSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/FeaturedRemixContestsSection.tsx @@ -8,10 +8,10 @@ import { import { useIsMobile } from 'hooks/useIsMobile' import { Carousel } from './Carousel' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const FeaturedRemixContestsSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Featured Remix Contests') const { data, isLoading, isError, isSuccess } = useExploreContent({ enabled: inView diff --git a/packages/web/src/pages/search-explore-page/components/desktop/FeelingLuckySection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/FeelingLuckySection.tsx index 477fb1295e2..a5d2280882e 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/FeelingLuckySection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/FeelingLuckySection.tsx @@ -13,10 +13,10 @@ import { TrackTile as MobileTrackTile } from 'components/track/mobile/TrackTile' import { TrackTileSize } from 'components/track/types' import { useIsMobile } from 'hooks/useIsMobile' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const FeelingLuckySection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Feeling Lucky') const isMobile = useIsMobile() const { data: feelingLuckyTrack, diff --git a/packages/web/src/pages/search-explore-page/components/desktop/LabelSpotlightSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/LabelSpotlightSection.tsx index ecd1f2d0789..54798769302 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/LabelSpotlightSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/LabelSpotlightSection.tsx @@ -5,10 +5,10 @@ import { UserCard, UserCardSkeleton } from 'components/user-card' import { useIsMobile } from 'hooks/useIsMobile' import { Carousel } from './Carousel' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const LabelSpotlightSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Label Spotlight') const { data, isLoading, isError, isSuccess } = useExploreContent({ enabled: inView }) diff --git a/packages/web/src/pages/search-explore-page/components/desktop/MoodGrid.tsx b/packages/web/src/pages/search-explore-page/components/desktop/MoodGrid.tsx index 0d24b55f6cb..bbb16dd0472 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/MoodGrid.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/MoodGrid.tsx @@ -1,6 +1,8 @@ import { useCallback } from 'react' +import { useAnalytics } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' +import { Name } from '@audius/common/models' import { Flex, Paper, Text, useTheme } from '@audius/harmony' import { Mood } from '@audius/sdk' @@ -9,25 +11,37 @@ import { useSearchCategory } from 'pages/search-page/hooks' import { labelByCategoryView } from 'pages/search-page/types' import { MOODS } from 'utils/Moods' +import { useExploreSectionTracking } from './useExploreSectionTracking' + export const MoodGrid = () => { + const { ref } = useExploreSectionTracking('Mood Grid') const [category, setCategory] = useSearchCategory() const { color } = useTheme() + const { trackEvent } = useAnalytics() const isMobile = useIsMobile() const handleMoodPress = useCallback( (mood: Mood) => { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: 'Mood Grid', + source: isMobile ? 'mobile' : 'web', + kind: 'mood', + link: mood + }) if (category === 'all') { setCategory('tracks', { mood }) } else { setCategory(category, { mood }) } }, - [category, setCategory] + [category, setCategory, trackEvent, isMobile] ) return ( { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Most Shared') const { data, isLoading, isError, isSuccess } = useMostSharedTracks( { pageSize: 10, diff --git a/packages/web/src/pages/search-explore-page/components/desktop/QuickSearchGrid.tsx b/packages/web/src/pages/search-explore-page/components/desktop/QuickSearchGrid.tsx index bbce30b8817..fc69f1589b7 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/QuickSearchGrid.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/QuickSearchGrid.tsx @@ -1,6 +1,8 @@ import React, { useCallback, useMemo } from 'react' +import { useAnalytics } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' +import { Name } from '@audius/common/models' import { QUICK_SEARCH_PRESETS, QuickSearchPreset, @@ -12,15 +14,24 @@ import { useIsMobile } from 'hooks/useIsMobile' import { useSearchCategory } from 'pages/search-page/hooks' import { MOODS } from 'utils/Moods' +import { useExploreSectionTracking } from './useExploreSectionTracking' + const QuickSearchPresetButton = ({ preset, - onClick + onClick, + onTrackClick }: { preset: QuickSearchPreset onClick: () => void + onTrackClick: () => void }) => { const { color } = useTheme() + const handleClick = useCallback(() => { + onTrackClick() + onClick() + }, [onClick, onTrackClick]) + const parts = useMemo(() => { const parts: React.ReactNode[] = [] if (preset.genre) { @@ -72,7 +83,7 @@ const QuickSearchPresetButton = ({ gap='s' border='default' backgroundColor='white' - onClick={onClick} + onClick={handleClick} css={{ ':hover': { background: color.neutral.n25, @@ -101,11 +112,20 @@ const QuickSearchPresetButton = ({ } export const QuickSearchGrid = () => { + const { ref } = useExploreSectionTracking('Quick Search') const isMobile = useIsMobile() const [, setCategory] = useSearchCategory() + const { trackEvent } = useAnalytics() const handleClickPreset = useCallback( (preset: QuickSearchPreset) => { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: 'Quick Search', + source: isMobile ? 'mobile' : 'web', + kind: 'preset', + link: `${preset.genre || ''}-${preset.mood || ''}-${preset.key || ''}` + }) setCategory('tracks', { mood: preset.mood, genre: preset.genre, @@ -114,11 +134,12 @@ export const QuickSearchGrid = () => { key: preset.key }) }, - [setCategory] + [setCategory, trackEvent, isMobile] ) return ( { handleClickPreset(preset)} + onTrackClick={() => {}} preset={preset} /> ))} diff --git a/packages/web/src/pages/search-explore-page/components/desktop/RecentPremiumTracksSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/RecentPremiumTracksSection.tsx index fabe0ffa751..fc570ae0fac 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/RecentPremiumTracksSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/RecentPremiumTracksSection.tsx @@ -3,10 +3,10 @@ import { exploreMessages as messages } from '@audius/common/messages' import { Carousel } from './Carousel' import { TilePairs, TileSkeletons } from './TileHelpers' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const RecentPremiumTracksSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Recent Premium Tracks') const { data, isLoading, isError, isSuccess } = useRecentPremiumTracks( { pageSize: 10 }, { enabled: inView } diff --git a/packages/web/src/pages/search-explore-page/components/desktop/RecentSearchesSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/RecentSearchesSection.tsx index ae4dd6b9c76..8bd91ffa43d 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/RecentSearchesSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/RecentSearchesSection.tsx @@ -3,10 +3,10 @@ import { Flex } from '@audius/harmony' import { useIsMobile } from 'hooks/useIsMobile' import { RecentSearches } from 'pages/search-page/RecentSearches' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const RecentSearchesSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Recent Searches') const isMobile = useIsMobile() return ( diff --git a/packages/web/src/pages/search-explore-page/components/desktop/RecentlyPlayedSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/RecentlyPlayedSection.tsx index 5129f05ffea..e4a8ea2a399 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/RecentlyPlayedSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/RecentlyPlayedSection.tsx @@ -5,10 +5,10 @@ import { TrackCard, TrackCardSkeleton } from 'components/track/TrackCard' import { useIsMobile } from 'hooks/useIsMobile' import { Carousel } from './Carousel' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const RecentlyPlayedSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Recently Played') const { data, isLoading, isError, isSuccess } = useRecentlyPlayedTracks( { pageSize: 10 }, { enabled: inView } diff --git a/packages/web/src/pages/search-explore-page/components/desktop/RecommendedTracksSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/RecommendedTracksSection.tsx index a066ba8433e..7931dd42ff6 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/RecommendedTracksSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/RecommendedTracksSection.tsx @@ -4,10 +4,10 @@ import { full } from '@audius/sdk' import { Carousel } from './Carousel' import { TilePairs, TileSkeletons } from './TileHelpers' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const RecommendedTracksSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Recommended Tracks') const { data, isLoading, isError, isSuccess } = useRecommendedTracks( { pageSize: 10, diff --git a/packages/web/src/pages/search-explore-page/components/desktop/TrackArtCard.tsx b/packages/web/src/pages/search-explore-page/components/desktop/TrackArtCard.tsx index f329a006a77..03744f91c30 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/TrackArtCard.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/TrackArtCard.tsx @@ -1,7 +1,13 @@ import { useCallback } from 'react' import { useRemixContest, useTrack } from '@audius/common/api' -import { ID, SquareSizes } from '@audius/common/models' +import { useAnalytics } from '@audius/common/hooks' +import { + ID, + SquareSizes, + ExploreSectionName, + Name +} from '@audius/common/models' import { dayjs, formatContestDeadlineWithStatus } from '@audius/common/utils' import { Flex, Skeleton, Text } from '@audius/harmony' import { useNavigate } from 'react-router' @@ -10,9 +16,11 @@ import { TrackLink } from 'components/link/TrackLink' import { UserLink } from 'components/link/UserLink' import PerspectiveCard from 'components/perspective-card/PerspectiveCard' import { TrackArtwork } from 'components/track/TrackArtwork' +import { useIsMobile } from 'hooks/useIsMobile' type TrackArtCardProps = { id: ID + sectionName?: ExploreSectionName } const messages = { @@ -29,8 +37,10 @@ const messages = { const ARTWORK_SIZE = 240 -export const TrackArtCard = ({ id }: TrackArtCardProps) => { +export const TrackArtCard = ({ id, sectionName }: TrackArtCardProps) => { const navigate = useNavigate() + const { trackEvent } = useAnalytics() + const isMobile = useIsMobile() const { data: track, isLoading: isTrackLoading } = useTrack(id) const { data: contest, isLoading: isContestLoading } = useRemixContest(id) @@ -39,8 +49,18 @@ export const TrackArtCard = ({ id }: TrackArtCardProps) => { const goToTrack = useCallback(() => { if (!track?.permalink) return + if (sectionName) { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: sectionName, + source: isMobile ? 'mobile' : 'web', + id, + kind: 'track', + link: track.permalink + }) + } navigate(track.permalink) - }, [navigate, track?.permalink]) + }, [navigate, track?.permalink, sectionName, id, trackEvent, isMobile]) if (!track) return null diff --git a/packages/web/src/pages/search-explore-page/components/desktop/TrendingPlaylistsSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/TrendingPlaylistsSection.tsx index 2ca65ab9c2e..b400fd2e7ac 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/TrendingPlaylistsSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/TrendingPlaylistsSection.tsx @@ -17,7 +17,7 @@ import { MOBILE_TILE_WIDTH, TILE_WIDTH } from './constants' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' type TileType = typeof DesktopCollectionTile | typeof MobileCollectionTile @@ -119,7 +119,7 @@ const CollectionLineupCarousel = ({ } export const TrendingPlaylistsSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking('Trending Playlists') const isMobile = useIsMobile() const size = isMobile ? TrackTileSize.SMALL : TrackTileSize.LARGE const { lineup, isError, isSuccess, isLoading, play, pause, togglePlay } = diff --git a/packages/web/src/pages/search-explore-page/components/desktop/UndergroundTrendingTracksSection.tsx b/packages/web/src/pages/search-explore-page/components/desktop/UndergroundTrendingTracksSection.tsx index d5b5d1e108b..ad03c21d18e 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/UndergroundTrendingTracksSection.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/UndergroundTrendingTracksSection.tsx @@ -6,10 +6,12 @@ import { Status } from '@audius/common/models' import { Carousel } from './Carousel' import { TilePairs, TileSkeletons } from './TileHelpers' -import { useDeferredElement } from './useDeferredElement' +import { useExploreSectionTracking } from './useExploreSectionTracking' export const UndergroundTrendingTracksSection = () => { - const { ref, inView } = useDeferredElement() + const { ref, inView } = useExploreSectionTracking( + 'Underground Trending Tracks' + ) const { data: undergroundTrendingTracks, isLoading, diff --git a/packages/web/src/pages/search-explore-page/components/desktop/UserArtCard.tsx b/packages/web/src/pages/search-explore-page/components/desktop/UserArtCard.tsx index 5b68ed06a18..62a50119e6a 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/UserArtCard.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/UserArtCard.tsx @@ -2,7 +2,13 @@ import { useCallback, useEffect } from 'react' import { useUser } from '@audius/common/api' import { imageBlank as placeholderArt } from '@audius/common/assets' -import { SquareSizes, ID } from '@audius/common/models' +import { useAnalytics } from '@audius/common/hooks' +import { + SquareSizes, + ID, + ExploreSectionName, + Name +} from '@audius/common/models' import { formatCount, route } from '@audius/common/utils' import cn from 'classnames' import { connect } from 'react-redux' @@ -38,6 +44,7 @@ type OwnProps = { index: number isLoading?: boolean setDidLoad?: (index: number) => void + sectionName?: ExploreSectionName } type UserArtCardProps = OwnProps & @@ -58,14 +65,27 @@ const UserArtCard = g( user, setFollowerUser, setModalVisibility, - goToRoute + goToRoute, + sectionName }) => { const { user_id, name, handle, follower_count } = user + const { trackEvent } = useAnalytics() + const isMobile = typeof window !== 'undefined' && window.innerWidth < 768 const goToProfile = useCallback(() => { + if (sectionName) { + trackEvent({ + eventName: Name.EXPLORE_SECTION_CLICK, + section: sectionName, + source: isMobile ? 'mobile' : 'web', + id: user_id, + kind: 'profile', + link: profilePage(handle) + }) + } const link = profilePage(handle) goToRoute(link) - }, [handle, goToRoute]) + }, [handle, goToRoute, sectionName, user_id, trackEvent, isMobile]) const onClickFollowers = useCallback(() => { setFollowerUser(user_id) diff --git a/packages/web/src/pages/search-explore-page/components/desktop/useExploreSectionTracking.ts b/packages/web/src/pages/search-explore-page/components/desktop/useExploreSectionTracking.ts new file mode 100644 index 00000000000..b5237ea450b --- /dev/null +++ b/packages/web/src/pages/search-explore-page/components/desktop/useExploreSectionTracking.ts @@ -0,0 +1,29 @@ +import { useEffect } from 'react' + +import { useAnalytics } from '@audius/common/hooks' +import { ExploreSectionName, Name } from '@audius/common/models' + +import { useIsMobile } from 'hooks/useIsMobile' + +import { useDeferredElement } from './useDeferredElement' + +/** + * Hook to track explore section impressions when they come into view + */ +export const useExploreSectionTracking = (sectionName: ExploreSectionName) => { + const { ref, inView } = useDeferredElement() + const { trackEvent } = useAnalytics() + const isMobile = useIsMobile() + + useEffect(() => { + if (inView) { + trackEvent({ + eventName: Name.EXPLORE_SECTION_VIEW, + section: sectionName, + source: isMobile ? 'mobile' : 'web' + }) + } + }, [inView, sectionName, isMobile, trackEvent]) + + return { ref, inView } +} From 16b8b26f8dd743a917ca3a2c6d50fdad0cb1e2e8 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Fri, 6 Feb 2026 11:23:33 -0800 Subject: [PATCH 2/2] Lint fix --- .../screens/explore-screen/hooks/useExploreSectionTracking.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts b/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts index 70a4a10434e..14554fcea17 100644 --- a/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts +++ b/packages/mobile/src/screens/explore-screen/hooks/useExploreSectionTracking.ts @@ -1,7 +1,8 @@ import { useEffect } from 'react' import { useAnalytics } from '@audius/common/hooks' -import { ExploreSectionName, Name } from '@audius/common/models' +import type { ExploreSectionName } from '@audius/common/models' +import { Name } from '@audius/common/models' import { useDeferredElement } from 'app/hooks/useDeferredElement'