diff --git a/packages/common/src/services/remote-config/feature-flags.ts b/packages/common/src/services/remote-config/feature-flags.ts index 0583599ccfe..dcc312d9c87 100644 --- a/packages/common/src/services/remote-config/feature-flags.ts +++ b/packages/common/src/services/remote-config/feature-flags.ts @@ -16,7 +16,8 @@ export enum FeatureFlags { COLLAPSED_EXPLORE_HEADER = 'collapsed_explore_header', LAUNCHPAD_VERIFICATION = 'launchpad_verification', FAN_CLUB_TEXT_POST_POSTING = 'fan_club_text_post_posting', - QUEUE_NEW_FEATURE_BADGE = 'queue_new_feature_badge' + QUEUE_NEW_FEATURE_BADGE = 'queue_new_feature_badge', + DISCOVER_WEEKLY = 'discover_weekly' } type FlagDefaults = Record @@ -26,7 +27,8 @@ export const environmentFlagDefaults: Record< Partial > = { development: { - [FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: true + [FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: true, + [FeatureFlags.DISCOVER_WEEKLY]: true }, production: {} } @@ -49,5 +51,6 @@ export const flagDefaults: FlagDefaults = { [FeatureFlags.COLLAPSED_EXPLORE_HEADER]: false, [FeatureFlags.LAUNCHPAD_VERIFICATION]: true, [FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: false, - [FeatureFlags.QUEUE_NEW_FEATURE_BADGE]: false + [FeatureFlags.QUEUE_NEW_FEATURE_BADGE]: false, + [FeatureFlags.DISCOVER_WEEKLY]: false } diff --git a/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx b/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx index b01bb2b2868..a5bf2efc0ff 100644 --- a/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx +++ b/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx @@ -1,9 +1,10 @@ import React, { useCallback, useEffect, useRef } from 'react' import { useDiscoverWeekly } from '@audius/common/api' -import { useAnalytics } from '@audius/common/hooks' +import { useAnalytics, useFeatureFlag } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' import { Name, type DiscoverWeeklySurface } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { Image } from 'react-native' import { Flex, Paper, Text } from '@audius/harmony-native' @@ -33,9 +34,12 @@ export const DiscoverWeekly = ({ const { InViewWrapper, inView } = useExploreSectionTracking('Discover Weekly') const navigation = useNavigation() const { trackEvent } = useAnalytics() + const { isEnabled: isDiscoverWeeklyEnabled } = useFeatureFlag( + FeatureFlags.DISCOVER_WEEKLY + ) const { trackIds, isError, isSuccess } = useDiscoverWeekly( { limit: 30 }, - { enabled: inView } + { enabled: inView && isDiscoverWeeklyEnabled } ) // Fire the impression once, and only once there's a real mix behind it. @@ -61,7 +65,13 @@ export const DiscoverWeekly = ({ navigation.navigate('DiscoverWeeklyScreen') }, [navigation, trackEvent, surface, trackIds.length]) - if (isError || (isSuccess && trackIds.length === 0)) { + // The flag check sits with the empty/error case so both surfaces that render + // this banner -- Explore and the feed -- are gated by this one return. + if ( + !isDiscoverWeeklyEnabled || + isError || + (isSuccess && trackIds.length === 0) + ) { return null } diff --git a/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx b/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx index 1e6c4ede2ad..426ad778273 100644 --- a/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx +++ b/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx @@ -1,9 +1,10 @@ import { useCallback, useEffect, useRef } from 'react' import { useDiscoverWeekly } from '@audius/common/api' -import { useAnalytics } from '@audius/common/hooks' +import { useAnalytics, useFeatureFlag } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' import { Name, type DiscoverWeeklySurface } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { route } from '@audius/common/utils' import { Artwork, @@ -53,9 +54,13 @@ export const DiscoverWeeklyBanner = ({ fallbackInView: true }) + const { isEnabled: isDiscoverWeeklyEnabled } = useFeatureFlag( + FeatureFlags.DISCOVER_WEEKLY + ) + const { trackIds, isError, isSuccess } = useDiscoverWeekly( { limit: 30 }, - { enabled: inView } + { enabled: inView && isDiscoverWeeklyEnabled } ) // Fire the impression once, and only once there's a real mix behind it -- @@ -84,8 +89,13 @@ export const DiscoverWeeklyBanner = ({ }, [navigate, trackEvent, surface, isMobile, trackIds.length]) // Hidden entirely when there's no mix to promote -- a banner advertising an - // empty page is worse than no banner. - if (isError || (isSuccess && trackIds.length === 0)) { + // empty page is worse than no banner. The flag check sits alongside it so + // every surface that renders the banner is gated by this one return. + if ( + !isDiscoverWeeklyEnabled || + isError || + (isSuccess && trackIds.length === 0) + ) { return null } diff --git a/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx b/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx index 023fa63299f..00fc91c94b8 100644 --- a/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx +++ b/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx @@ -1,11 +1,13 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' import { useCurrentUserId, useDiscoverWeekly } from '@audius/common/api' -import { useAnalytics } from '@audius/common/hooks' +import { useAnalytics, useFeatureFlag } from '@audius/common/hooks' import { exploreMessages } from '@audius/common/messages' import { ID, Name, PlaybackSource } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { playbackActions, playbackSelectors } from '@audius/common/store' import type { PlaybackTrack } from '@audius/common/store' +import { route } from '@audius/common/utils' import { Artwork, Button, @@ -15,6 +17,7 @@ import { Text } from '@audius/harmony' import { useDispatch, useSelector } from 'react-redux' +import { Navigate } from 'react-router' import discoverWeeklyArt from 'assets/img/discoverWeekly.jpg' import { make } from 'common/store/analytics/actions' @@ -30,6 +33,8 @@ const messages = { 'A fresh mix of tracks picked for you, updated every Monday on Audius.' } +const { EXPLORE_PAGE } = route + const DISCOVER_WEEKLY_SOURCE = 'DISCOVER_WEEKLY_TRACKS' const PAGE_SIZE = 30 const ARTWORK_SIZE = 200 @@ -61,9 +66,16 @@ export const DiscoverWeeklyPage = () => { const mainContentRef = useMainContentRef() const { data: currentUserId } = useCurrentUserId() - const { trackIds, isPending, isFetching, isLoading } = useDiscoverWeekly({ - limit: PAGE_SIZE - }) + // The route stays registered while the flag is off -- the URL is public and + // shareable, so a link that predates the rollout should land somewhere real + // rather than 404. + const { isEnabled: isDiscoverWeeklyEnabled, isLoaded: isFlagLoaded } = + useFeatureFlag(FeatureFlags.DISCOVER_WEEKLY) + + const { trackIds, isPending, isFetching, isLoading } = useDiscoverWeekly( + { limit: PAGE_SIZE }, + { enabled: isDiscoverWeeklyEnabled } + ) // Fired once the mix resolves rather than on mount, so trackCount is real // and a failed load doesn't register as a page view. @@ -140,6 +152,11 @@ export const DiscoverWeeklyPage = () => { const isEmpty = !isLoading && trackIds.length === 0 + // Nothing until remote config resolves, so an enabled user doesn't get + // bounced to Explore on the first frame. + if (!isFlagLoaded) return null + if (!isDiscoverWeeklyEnabled) return + return (