Skip to content

Commit 19696ab

Browse files
dylanjeffersclaude
andauthored
chore: drop CONTESTS feature flag (#14317)
## Summary - Removes the `FeatureFlags.CONTESTS` feature flag (and its enum entry + default in `feature-flags.ts`) and all branches that gated on it across web and mobile. - Keeps the new dedicated contest experience (`/contests`, `/:handle/contest/:slug`, `TrackContestsSection`, `ContestCard`, etc.) as the only path; deletes the legacy in-line contest UI that was the flag-off fallback. - Cleans up dead code: `RemixContestSection` (desktop + mobile + RN), `RemixContestCountdown` (web + RN), `RemixContestTeaser`, `RemixContestFlair`, `RemixContestCard` (web + RN), their Details/Prizes/Submissions/Winners tab variants, the `IconContestSign` RN re-export, and the flag-asserting `TrackPageContestWiring.test.ts`. ## Test plan - [ ] `npm run verify` on touched packages (`common`, `web`, `mobile`) - [ ] Smoke web: `/contests` discovery page loads, profile contests tab still appears when a host runs a contest, track page surfaces `TrackContestsSection`, dedicated contest page renders without flag gate - [ ] Smoke mobile: contests drawer item appears, `ContestsScreen` loads, profile contests tab works, track screen shows `TrackContestsSection`, `ContestScreen` no longer flag-gated 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 472e2e3 commit 19696ab

48 files changed

Lines changed: 73 additions & 2148 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/common/src/services/remote-config/feature-flags.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export enum FeatureFlags {
1616
COLLAPSED_EXPLORE_HEADER = 'collapsed_explore_header',
1717
LAUNCHPAD_VERIFICATION = 'launchpad_verification',
1818
FAN_CLUB_TEXT_POST_POSTING = 'fan_club_text_post_posting',
19-
CONTESTS = 'contests',
2019
QUEUE_NEW_FEATURE_BADGE = 'queue_new_feature_badge'
2120
}
2221

@@ -50,6 +49,5 @@ export const flagDefaults: FlagDefaults = {
5049
[FeatureFlags.COLLAPSED_EXPLORE_HEADER]: false,
5150
[FeatureFlags.LAUNCHPAD_VERIFICATION]: true,
5251
[FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: false,
53-
[FeatureFlags.CONTESTS]: false,
5452
[FeatureFlags.QUEUE_NEW_FEATURE_BADGE]: false
5553
}

packages/mobile/src/components/remix-carousel/RemixContestCard.tsx

Lines changed: 0 additions & 81 deletions
This file was deleted.

packages/mobile/src/components/remix-carousel/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/mobile/src/components/track-flair/TrackFlair.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import type { ReactNode } from 'react'
22

3-
import { useRemixContest, useTrack } from '@audius/common/api'
4-
import { useFeatureFlag } from '@audius/common/hooks'
3+
import { useTrack } from '@audius/common/api'
54
import type { ID } from '@audius/common/models'
6-
import { FeatureFlags } from '@audius/common/services'
75
import type { StyleProp, ViewStyle } from 'react-native'
86
import { View } from 'react-native'
97

10-
import { IconCosign, IconContestSign } from '@audius/harmony-native'
8+
import { IconCosign } from '@audius/harmony-native'
119

1210
import { Size } from './types'
1311

@@ -81,19 +79,10 @@ export const TrackFlair = ({ size, children, style, trackId }: CoSignProps) => {
8179
)
8280
}
8381
})
84-
const { data: remixContest } = useRemixContest(trackId)
85-
// When CONTESTS is on, the contest experience moved to its own screen
86-
// and the trophy flair on the artwork is an orphaned indicator on what
87-
// should be a clean tile (Figma 2888-16639).
88-
const { isEnabled: isContestsEnabled } = useFeatureFlag(FeatureFlags.CONTESTS)
8982

9083
const { size: iconSize, position } = layoutBySize[size]
9184

92-
const FlairIcon = isCosign
93-
? IconCosign
94-
: remixContest?.endDate && !isContestsEnabled
95-
? IconContestSign
96-
: null
85+
const FlairIcon = isCosign ? IconCosign : null
9786

9887
return (
9988
<View style={style}>

packages/mobile/src/harmony-native/icons.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ export { default as IconCloudDownloadPaused } from '@audius/harmony/src/assets/i
9595
export { default as IconNotificationOn } from '@audius/harmony/src/assets/icons/NotificationOn.svg'
9696
export { default as IconTrophy } from '@audius/harmony/src/assets/icons/Trophy.svg'
9797
export { default as IconCosign } from '@audius/harmony/src/assets/icons/Cosign.svg'
98-
export { default as IconContestSign } from '@audius/harmony/src/assets/icons/ContestSign.svg'
9998
export { default as IconCloudDownloadQueued } from '@audius/harmony/src/assets/icons/CloudDownloadQueued.svg'
10099
export { default as IconPause } from '@audius/harmony/src/assets/icons/Pause.svg'
101100
export { default as IconTurntable } from '@audius/harmony/src/assets/icons/Turntable.svg'

packages/mobile/src/screens/app-drawer-screen/left-nav-drawer/ContestsNavItem.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import React from 'react'
22

3-
import { useFeatureFlag } from '@audius/common/hooks'
4-
import { FeatureFlags } from '@audius/common/services'
5-
63
import { IconTrophy } from '@audius/harmony-native'
74

85
import { LeftNavLink } from './LeftNavLink'
@@ -12,12 +9,6 @@ const messages = {
129
}
1310

1411
export const ContestsNavItem = () => {
15-
const { isEnabled: isContestsPageEnabled } = useFeatureFlag(
16-
FeatureFlags.CONTESTS
17-
)
18-
19-
if (!isContestsPageEnabled) return null
20-
2112
return (
2213
<LeftNavLink icon={IconTrophy} label={messages.contests} to='Contests' />
2314
)

packages/mobile/src/screens/contest-screen/ContestScreen.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ import {
2121
useUnfollowEvent,
2222
useUser
2323
} from '@audius/common/api'
24-
import { useFeatureFlag } from '@audius/common/hooks'
2524
import { Name, ShareSource } from '@audius/common/models'
26-
import { FeatureFlags } from '@audius/common/services'
2725
import { shareModalUIActions } from '@audius/common/store'
2826
import { dayjs, getLocalTimezone } from '@audius/common/utils'
2927
import { PortalHost } from '@gorhom/portal'
@@ -164,9 +162,6 @@ export const ContestScreen = () => {
164162
const navigation = useNavigation()
165163
const insets = useSafeAreaInsets()
166164

167-
const { isEnabled: isContestsEnabled, isLoaded: isFlagLoaded } =
168-
useFeatureFlag(FeatureFlags.CONTESTS)
169-
170165
const { data: track } = useTrackByParams(params ?? {})
171166
const trackId = track?.track_id
172167
const { data: user } = useUser(track?.owner_id)
@@ -338,11 +333,6 @@ export const ContestScreen = () => {
338333
navigation.setOptions({ headerShown: false })
339334
}, [navigation])
340335

341-
if (isFlagLoaded && !isContestsEnabled) {
342-
navigation.goBack()
343-
return null
344-
}
345-
346336
if (!track || !user || !contest || !eventId || trackId == null) {
347337
return (
348338
<Screen>

packages/mobile/src/screens/contests-screen/ContestsScreen.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React, { useCallback } from 'react'
22

33
import { useAllRemixContests } from '@audius/common/api'
4-
import { useFeatureFlag } from '@audius/common/hooks'
5-
import { FeatureFlags } from '@audius/common/services'
64

75
import {
86
Box,
@@ -23,9 +21,6 @@ const HERO_SKELETON_COUNT = 1
2321
const GRID_SKELETON_COUNT = 4
2422

2523
export const ContestsScreen = () => {
26-
const { isEnabled: isContestsPageEnabled } = useFeatureFlag(
27-
FeatureFlags.CONTESTS
28-
)
2924
const {
3025
data,
3126
isPending,
@@ -34,12 +29,11 @@ export const ContestsScreen = () => {
3429
hasNextPage,
3530
fetchNextPage,
3631
isFetchingNextPage
37-
} = useAllRemixContests(undefined, { enabled: isContestsPageEnabled })
32+
} = useAllRemixContests()
3833

39-
const contests = isContestsPageEnabled ? (data ?? []) : []
34+
const contests = data ?? []
4035
const [heroTrackId, ...gridTrackIds] = contests
41-
const showSkeletons =
42-
isContestsPageEnabled && (isPending || (!isSuccess && !isError))
36+
const showSkeletons = isPending || (!isSuccess && !isError)
4337
const showEmpty = isSuccess && contests.length === 0
4438

4539
const handleEndReached = useCallback(() => {

packages/mobile/src/screens/explore-screen/components/FeaturedRemixContests.tsx

Lines changed: 11 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import React from 'react'
22

3-
import {
4-
useAllRemixContests,
5-
useExploreContent,
6-
useTracks
7-
} from '@audius/common/api'
8-
import { useFeatureFlag } from '@audius/common/hooks'
3+
import { useAllRemixContests } from '@audius/common/api'
94
import { exploreMessages as messages } from '@audius/common/messages'
10-
import { FeatureFlags } from '@audius/common/services'
115

126
import { useTheme } from '@audius/harmony-native'
137
import { ContestCard } from 'app/components/contest-card'
148
import { CardList } from 'app/components/core'
15-
import { RemixContestCard } from 'app/components/remix-carousel/RemixContestCard'
169
import { TrackCardSkeleton } from 'app/components/track/TrackCardSkeleton'
1710

1811
import { useDeferredElement } from '../../../hooks/useDeferredElement'
@@ -22,59 +15,21 @@ import { ExploreSection } from './ExploreSection'
2215
export const FeaturedRemixContests = () => {
2316
const { spacing } = useTheme()
2417
const { InViewWrapper, inView } = useDeferredElement()
25-
const { isEnabled: isContestsPageEnabled } = useFeatureFlag(
26-
FeatureFlags.CONTESTS
27-
)
2818

29-
// When the contests feature is on the section title is just
30-
// "Contests" — switch the data source from the curated featured
31-
// list to the full live list so the carousel actually matches the
32-
// label (Julian: "the full list of contests isn't showing on the
33-
// mobile discovery page"). Backed by `useAllRemixContests`, the same
34-
// source the dedicated /contests screen uses.
3519
const { data: allContestTrackIds, isPending: isAllContestsPending } =
36-
useAllRemixContests(undefined, { enabled: inView && isContestsPageEnabled })
37-
38-
const { data: exploreContent, isPending: isExplorePending } =
39-
useExploreContent({ enabled: inView && !isContestsPageEnabled })
40-
41-
// Old-card path needs the hydrated track list; new-card path resolves per
42-
// card internally so we skip this fetch when the flag is enabled.
43-
const { data: remixContests } = useTracks(
44-
exploreContent?.featuredRemixContests,
45-
{ enabled: inView && !isContestsPageEnabled }
46-
)
20+
useAllRemixContests(undefined, { enabled: inView })
4721

4822
return (
4923
<InViewWrapper>
50-
<ExploreSection
51-
title={
52-
isContestsPageEnabled
53-
? messages.contests
54-
: messages.featuredRemixContests
55-
}
56-
>
57-
{isContestsPageEnabled ? (
58-
<CardList
59-
data={(allContestTrackIds ?? []).map((trackId) => ({ trackId }))}
60-
renderItem={({ item }) => <ContestCard trackId={item.trackId} />}
61-
horizontal
62-
carouselSpacing={spacing.l}
63-
isLoading={isAllContestsPending}
64-
LoadingCardComponent={TrackCardSkeleton}
65-
/>
66-
) : (
67-
<CardList
68-
data={remixContests?.map((track) => ({ trackId: track.track_id }))}
69-
renderItem={({ item }) => (
70-
<RemixContestCard trackId={item.trackId} />
71-
)}
72-
horizontal
73-
carouselSpacing={spacing.l}
74-
isLoading={isExplorePending}
75-
LoadingCardComponent={TrackCardSkeleton}
76-
/>
77-
)}
24+
<ExploreSection title={messages.contests}>
25+
<CardList
26+
data={(allContestTrackIds ?? []).map((trackId) => ({ trackId }))}
27+
renderItem={({ item }) => <ContestCard trackId={item.trackId} />}
28+
horizontal
29+
carouselSpacing={spacing.l}
30+
isLoading={isAllContestsPending}
31+
LoadingCardComponent={TrackCardSkeleton}
32+
/>
7833
</ExploreSection>
7934
</InViewWrapper>
8035
)

packages/mobile/src/screens/profile-screen/ProfileTabs/ProfileTabNavigator.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { ReactElement } from 'react'
22

33
import { useProfileUser, useUserHasRemixContest } from '@audius/common/api'
4-
import { useFeatureFlag, useIsArtist } from '@audius/common/hooks'
5-
import { FeatureFlags } from '@audius/common/services'
4+
import { useIsArtist } from '@audius/common/hooks'
65
import { ProfilePageTabs } from '@audius/common/store'
76
import { useSafeAreaInsets } from 'react-native-safe-area-context'
87

@@ -56,11 +55,10 @@ export const ProfileTabNavigator = ({
5655
handle: params.handle
5756
}
5857
const isArtist = useIsArtist(params)
59-
const { isEnabled: isContestsEnabled } = useFeatureFlag(FeatureFlags.CONTESTS)
6058
const { hasContest: profileHasContest } = useUserHasRemixContest(
61-
isArtist && isContestsEnabled ? user_id : null
59+
isArtist ? user_id : null
6260
)
63-
const showContestsTab = isContestsEnabled && profileHasContest
61+
const showContestsTab = profileHasContest
6462

6563
const trackScreen = collapsibleTabScreen({
6664
name: ProfilePageTabs.TRACKS,
@@ -118,9 +116,9 @@ export const ProfileTabNavigator = ({
118116
{albumsScreen}
119117
{playlistsScreen}
120118
{repostsScreen}
121-
{/* Contests tab — gated by the CONTESTS flag AND on whether this
122-
host actually runs any remix contest. Hidden otherwise so the
123-
tab doesn't lead to an empty/unreachable destination. */}
119+
{/* Contests tab — gated on whether this host actually runs any
120+
remix contest. Hidden otherwise so the tab doesn't lead to an
121+
empty/unreachable destination. */}
124122
{showContestsTab ? contestsScreen : null}
125123
</CollapsibleTabNavigator>
126124
)

0 commit comments

Comments
 (0)