From 1526bb0c15e7454ed0d0775266cb7e7b4cddc5b7 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 5 Mar 2026 10:18:47 -0800 Subject: [PATCH] Finalize --- .../Button/FilterButton/FilterButton.tsx | 18 +- .../FilterButton/FilterButtonScreen.tsx | 5 +- .../components/Button/FilterButton/types.ts | 5 + .../track-screen/TrackRemixesScreen.tsx | 54 ++++- .../components/desktop/NewRemixesPage.tsx | 208 ---------------- .../components/desktop/RemixesPage.tsx | 226 +++++++++++++----- .../components/mobile/NewRemixesPage.tsx | 156 ------------ .../components/mobile/RemixesPage.tsx | 153 ++++++++---- 8 files changed, 347 insertions(+), 478 deletions(-) delete mode 100644 packages/web/src/pages/remixes-page/components/desktop/NewRemixesPage.tsx delete mode 100644 packages/web/src/pages/remixes-page/components/mobile/NewRemixesPage.tsx diff --git a/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButton.tsx b/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButton.tsx index dc08e4472af..605aa91def7 100644 --- a/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButton.tsx +++ b/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButton.tsx @@ -30,7 +30,8 @@ export const FilterButton = ( onChange, filterScreen = 'FilterButton', options, - screen + screen, + disableSearch } = props const selectedOption = options?.find((option) => option.value === value) @@ -113,11 +114,22 @@ export const FilterButton = ( title: label, onChange, value, - screen + screen, + disableSearch }) } }, - [onPress, options, screen, navigation, filterScreen, label, onChange, value] + [ + onPress, + options, + screen, + navigation, + filterScreen, + label, + onChange, + value, + disableSearch + ] ) const Icon = useMemo(() => { diff --git a/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButtonScreen.tsx b/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButtonScreen.tsx index d17ae3c1649..7e3bba1d8ab 100644 --- a/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButtonScreen.tsx +++ b/packages/mobile/src/harmony-native/components/Button/FilterButton/FilterButtonScreen.tsx @@ -15,6 +15,7 @@ export type FilterButtonScreenParams = { onChange: (value: Maybe) => void value: string screen?: ComponentType> + disableSearch?: boolean } export const FilterButtonScreen = () => { @@ -24,7 +25,8 @@ export const FilterButtonScreen = () => { title, onChange: onSubmit, value: initialValue, - screen: Screen + screen: Screen, + disableSearch = false } = params ?? {} const [value, setValue] = useState>(initialValue) @@ -47,6 +49,7 @@ export const FilterButtonScreen = () => { value={value ?? ''} onSubmit={handleSubmit} searchText={`Search ${title}`} + disableSearch={disableSearch} clearable={Boolean(value)} onClear={() => setValue(undefined)} /> diff --git a/packages/mobile/src/harmony-native/components/Button/FilterButton/types.ts b/packages/mobile/src/harmony-native/components/Button/FilterButton/types.ts index c1d1895bc5f..e207a1369b3 100644 --- a/packages/mobile/src/harmony-native/components/Button/FilterButton/types.ts +++ b/packages/mobile/src/harmony-native/components/Button/FilterButton/types.ts @@ -129,4 +129,9 @@ export type FilterButtonProps = { */ screen?: ComponentType> renderLabel?: (label: string) => ReactNode + + /** + * When true, the list selection screen will not show a search field (e.g. for short option lists) + */ + disableSearch?: boolean } diff --git a/packages/mobile/src/screens/track-screen/TrackRemixesScreen.tsx b/packages/mobile/src/screens/track-screen/TrackRemixesScreen.tsx index c43eae7508b..fe37662039e 100644 --- a/packages/mobile/src/screens/track-screen/TrackRemixesScreen.tsx +++ b/packages/mobile/src/screens/track-screen/TrackRemixesScreen.tsx @@ -1,3 +1,5 @@ +import { useCallback, useState } from 'react' + import { useCurrentUserId, useRemixContest, @@ -12,6 +14,7 @@ import { Text as RNText, View } from 'react-native' import { Button, + FilterButton, Flex, IconRemix, IconTrophy, @@ -56,17 +59,37 @@ const useStyles = makeStyles(({ spacing, palette, typography }) => ({ } })) +const SORT_OPTIONS = [ + { label: 'Most Recent', value: 'recent' as const }, + { label: 'Most Plays', value: 'plays' as const }, + { label: 'Most Favorites', value: 'likes' as const } +] + export const TrackRemixesScreen = () => { const { onOpen: openPickWinnersDrawer } = useDrawer('PickWinners') const { data: currentUserId } = useCurrentUserId() const { params } = useRoute<'TrackRemixes'>() const { data: track } = useTrackByParams(params) const trackId = track?.track_id + + const [sortMethod, setSortMethod] = useState<'recent' | 'plays' | 'likes'>( + 'recent' + ) + const [isCosign, setIsCosign] = useState(false) + const [isContestEntry, setIsContestEntry] = useState(false) + + const handleSortChange = useCallback((value: string | undefined) => { + setSortMethod((value as 'recent' | 'plays' | 'likes') ?? 'recent') + }, []) + const { data, count, isFetching, isPending, loadNextPage, lineup, pageSize } = useRemixesLineup({ trackId: track?.track_id, includeOriginal: true, - includeWinners: true + includeWinners: true, + sortMethod, + isCosign, + isContestEntry }) const { data: contest } = useRemixContest(trackId) const isRemixContest = !!contest @@ -94,13 +117,40 @@ export const TrackRemixesScreen = () => { ) const remixesDelineator = ( - + {count ? ( {messages.remixesTitle} {count !== undefined ? ` (${count})` : ''} ) : null} + + setIsCosign((prev) => !prev)} + onChange={(v) => setIsCosign(v === 'true')} + size='small' + /> + {isRemixContest ? ( + setIsContestEntry((prev) => !prev)} + onChange={(v) => setIsContestEntry(v === 'true')} + size='small' + /> + ) : null} + + ) diff --git a/packages/web/src/pages/remixes-page/components/desktop/NewRemixesPage.tsx b/packages/web/src/pages/remixes-page/components/desktop/NewRemixesPage.tsx deleted file mode 100644 index e7e2733854e..00000000000 --- a/packages/web/src/pages/remixes-page/components/desktop/NewRemixesPage.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { useCallback } from 'react' - -import { - useRemixesLineup, - useRemixContest, - useCurrentUserId, - useRemixes -} from '@audius/common/api' -import { remixMessages as messages } from '@audius/common/messages' -import { Name, Track, User } from '@audius/common/models' -import { remixesPageLineupActions } from '@audius/common/store' -import { dayjs } from '@audius/common/utils' -import { - IconRemix, - Text, - Flex, - FilterButton, - IconTrophy, - Button -} from '@audius/harmony' -import { Link } from 'react-router' - -import { MIN_PAGE_WIDTH_PX } from 'common/utils/layout' -import { Header } from 'components/header/desktop/Header' -import { TanQueryLineup } from 'components/lineup/TanQueryLineup' -import Page from 'components/page/Page' -import { useRemixPageParams } from 'pages/remixes-page/hooks' -import { useUpdateSearchParams } from 'pages/search-page/hooks' -import { track, make } from 'services/analytics' -import { fullTrackRemixesPage, pickWinnersPage } from 'utils/route' -import { withNullGuard } from 'utils/withNullGuard' - -import styles from './RemixesPage.module.css' - -export const REMIXES_PAGE_SIZE = 10 - -export type RemixesPageProps = { - title: string - count: number | null - originalTrack: - | Pick - | undefined - user: User | undefined - goToTrackPage: () => void - goToArtistPage: () => void -} - -const nullGuard = withNullGuard( - ({ originalTrack, user, ...p }: RemixesPageProps) => - originalTrack && user && { ...p, originalTrack, user } -) - -const RemixesPage = nullGuard(({ title, originalTrack }) => { - const updateSortParam = useUpdateSearchParams('sortMethod') - const updateIsCosignParam = useUpdateSearchParams('isCosign') - const updateIsContestEntryParam = useUpdateSearchParams('isContestEntry') - const { data: currentUserId } = useCurrentUserId() - const { data: contest } = useRemixContest(originalTrack?.track_id) - const winnerCount = contest?.eventData?.winners?.length ?? 0 - const { data: remixes } = useRemixes({ - trackId: originalTrack?.track_id, - isContestEntry: true - }) - const remixCount = remixes?.pages[0]?.count ?? 0 - - const isRemixContest = !!contest - const isTrackOwner = currentUserId === originalTrack.owner_id - const isRemixContestEnded = - isRemixContest && dayjs(contest.endDate).isBefore(dayjs()) - const showPickWinnersButton = - isTrackOwner && isRemixContestEnded && remixCount > 0 - - const { sortMethod, isCosign, isContestEntry } = useRemixPageParams() - const { - data, - count, - isFetching, - isPending, - isError, - hasNextPage, - play, - pause, - loadNextPage, - isPlaying, - lineup - } = useRemixesLineup({ - trackId: originalTrack?.track_id, - includeOriginal: true, - includeWinners: true, - sortMethod, - isCosign, - isContestEntry - }) - - const pickWinnersRoute = pickWinnersPage(originalTrack?.permalink) - - const handlePickWinnersClick = useCallback(() => { - if (contest?.eventId) { - track( - make({ - eventName: Name.REMIX_CONTEST_PICK_WINNERS_OPEN, - remixContestId: contest?.eventId, - trackId: originalTrack?.track_id - }) - ) - } - }, [contest?.eventId, originalTrack?.track_id]) - - const renderHeader = () => ( -
- - {winnerCount > 0 ? messages.editWinners : messages.pickWinners} - - - ) : null - } - /> - ) - - const winnersDelineator = ( - - {messages.winners} - - ) - - const remixesDelineator = ( - - - {messages.remixesTitle} - {count !== undefined ? ` (${count})` : ''} - - - updateIsCosignParam(isCosign ? '' : 'true')} - /> - {isRemixContest ? ( - - updateIsContestEntryParam(isContestEntry ? '' : 'true') - } - /> - ) : null} - - - - ) - - const delineatorMap = - winnerCount > 0 - ? { - 0: winnersDelineator, - [winnerCount]: remixesDelineator - } - : { - 0: remixesDelineator - } - - const maxEntries = count && winnerCount ? count + winnerCount + 1 : undefined - - return ( - - - {messages.originalTrack} - - - - ) -}) - -export default RemixesPage diff --git a/packages/web/src/pages/remixes-page/components/desktop/RemixesPage.tsx b/packages/web/src/pages/remixes-page/components/desktop/RemixesPage.tsx index 5de19fad17a..abe2c032a80 100644 --- a/packages/web/src/pages/remixes-page/components/desktop/RemixesPage.tsx +++ b/packages/web/src/pages/remixes-page/components/desktop/RemixesPage.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useCallback, useEffect } from 'react' import { useUser, @@ -6,38 +6,44 @@ import { useTrackByPermalink, useRemixContest, useRemixersCount, - useRemixesLineup + useRemixesLineup, + useCurrentUserId, + useRemixes } from '@audius/common/api' -import { remixMessages } from '@audius/common/messages' +import { remixMessages as messages } from '@audius/common/messages' +import { Name } from '@audius/common/models' import { - remixesPageLineupActions, remixesPageActions, + remixesPageLineupActions, remixesPageSelectors } from '@audius/common/store' -import { pluralize } from '@audius/common/utils' -import { IconRemix, Text } from '@audius/harmony' +import { dayjs } from '@audius/common/utils' +import { + IconRemix, + Text, + Flex, + FilterButton, + IconTrophy, + Button +} from '@audius/harmony' import { useDispatch, useSelector } from 'react-redux' -import { useParams } from 'react-router' +import { Link, useParams } from 'react-router' +import { MIN_PAGE_WIDTH_PX } from 'common/utils/layout' import { Header } from 'components/header/desktop/Header' import { TanQueryLineup } from 'components/lineup/TanQueryLineup' -import { TrackLink } from 'components/link/TrackLink' -import { UserLink } from 'components/link/UserLink' import Page from 'components/page/Page' -import { fullTrackRemixesPage } from 'utils/route' +import { useRemixPageParams } from 'pages/remixes-page/hooks' +import { useUpdateSearchParams } from 'pages/search-page/hooks' +import { track as trackEvent, make } from 'services/analytics' +import { fullTrackRemixesPage, pickWinnersPage } from 'utils/route' import styles from './RemixesPage.module.css' const { getTrackId } = remixesPageSelectors const { fetchTrackSucceeded, reset } = remixesPageActions -const messages = { - remixes: 'Remix', - by: 'by', - of: 'of', - getDescription: (trackName: string, artistName: string) => - `${messages.remixes} ${messages.of} ${trackName} ${messages.by} ${artistName}` -} +export const REMIXES_PAGE_SIZE = 10 type RemixesPageProps = { containerRef?: React.RefObject @@ -48,8 +54,7 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { const { handle, slug } = useParams<{ handle: string; slug: string }>() const originalTrackId = useSelector(getTrackId) const { data: originalTrack } = useTrack(originalTrackId) - const { data: remixContest } = useRemixContest(originalTrackId) - const { data: count = null } = useRemixersCount({ trackId: originalTrackId }) + useRemixersCount({ trackId: originalTrackId }) const { data: originalTrackByPermalink } = useTrackByPermalink( handle && slug ? `/${handle}/${slug}` : null @@ -58,22 +63,22 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { const { data: user } = useUser(track?.owner_id) const trackId = track?.track_id - useEffect(() => { - if (trackId) { - dispatch(fetchTrackSucceeded({ trackId })) - } - }, [dispatch, trackId]) - - useEffect(() => { - return function cleanup() { - dispatch(reset()) - dispatch(remixesPageLineupActions.reset()) - } - }, [dispatch]) + const updateSortParam = useUpdateSearchParams('sortMethod') + const updateIsCosignParam = useUpdateSearchParams('isCosign') + const updateIsContestEntryParam = useUpdateSearchParams('isContestEntry') + const { data: currentUserId } = useCurrentUserId() + const { data: contest } = useRemixContest(trackId) + const winnerCount = contest?.eventData?.winners?.length ?? 0 + const { data: remixes } = useRemixes({ + trackId: trackId ?? undefined, + isContestEntry: true + }) + const remixCount = remixes?.pages[0]?.count ?? 0 - // All hooks must be called before any early returns + const { sortMethod, isCosign, isContestEntry } = useRemixPageParams() const { data, + count: lineupCount, isFetching, isPending, isError, @@ -82,58 +87,155 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { pause, loadNextPage, isPlaying, - lineup, - pageSize + lineup } = useRemixesLineup({ - trackId: track?.track_id + trackId: trackId ?? undefined, + includeOriginal: true, + includeWinners: true, + sortMethod, + isCosign, + isContestEntry }) + useEffect(() => { + if (trackId) { + dispatch(fetchTrackSucceeded({ trackId })) + } + }, [dispatch, trackId]) + + useEffect(() => { + return function cleanup() { + dispatch(reset()) + dispatch(remixesPageLineupActions.reset()) + } + }, [dispatch]) + + const pickWinnersRoute = track ? pickWinnersPage(track.permalink) : '' + const handlePickWinnersClick = useCallback(() => { + if (contest?.eventId && track) { + trackEvent( + make({ + eventName: Name.REMIX_CONTEST_PICK_WINNERS_OPEN, + remixContestId: contest?.eventId, + trackId: track.track_id + }) + ) + } + }, [contest?.eventId, track]) + if (!track || !user) { return null } - const isRemixContest = !!remixContest + const isRemixContest = !!contest + const isTrackOwner = currentUserId === track.owner_id + const isRemixContestEnded = + isRemixContest && dayjs(contest.endDate).isBefore(dayjs()) + const showPickWinnersButton = + isTrackOwner && isRemixContestEnded && remixCount > 0 + const title = isRemixContest - ? remixMessages.submissionsTitle - : remixMessages.remixesTitle + ? messages.submissionsTitle + : messages.remixesTitle const renderHeader = () => (
- {count} {pluralize(messages.remixes, count, 'es', !count)}{' '} - {messages.of}{' '} - {' '} - {messages.by} - - } containerStyles={styles.header} + rightDecorator={ + showPickWinnersButton ? ( + + ) : null + } /> ) + const winnersDelineator = ( + + {messages.winners} + + ) + + const remixesDelineator = ( + + + {messages.remixesTitle} + {lineupCount !== undefined ? ` (${lineupCount})` : ''} + + + updateIsCosignParam(isCosign ? '' : 'true')} + /> + {isRemixContest ? ( + + updateIsContestEntryParam(isContestEntry ? '' : 'true') + } + /> + ) : null} + + + + ) + + const delineatorMap = + winnerCount > 0 + ? { + 0: winnersDelineator, + [winnerCount]: remixesDelineator + } + : { + 0: remixesDelineator + } + + const maxEntries = + lineupCount !== undefined && winnerCount + ? lineupCount + winnerCount + 1 + : undefined + return ( - + + {messages.originalTrack} + + ) } diff --git a/packages/web/src/pages/remixes-page/components/mobile/NewRemixesPage.tsx b/packages/web/src/pages/remixes-page/components/mobile/NewRemixesPage.tsx deleted file mode 100644 index 90e9c60e65a..00000000000 --- a/packages/web/src/pages/remixes-page/components/mobile/NewRemixesPage.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useEffect, useContext } from 'react' - -import { useRemixContest, useRemixesLineup } from '@audius/common/api' -import { remixMessages as messages } from '@audius/common/messages' -import { Track, User } from '@audius/common/models' -import { remixesPageLineupActions } from '@audius/common/store' -import { - Flex, - Text, - IconRemix as IconRemixes, - IconTrophy -} from '@audius/harmony' - -import Header from 'components/header/mobile/Header' -import { HeaderContext } from 'components/header/mobile/HeaderContextProvider' -import { TanQueryLineup } from 'components/lineup/TanQueryLineup' -import MobilePageContainer from 'components/mobile-page-container/MobilePageContainer' -import { useSubPageHeader } from 'components/nav/mobile/NavContext' -import { fullTrackRemixesPage } from 'utils/route' -import { withNullGuard } from 'utils/withNullGuard' - -import styles from './RemixesPage.module.css' - -export type RemixesPageProps = { - title: string - count: number | null - originalTrack: - | Pick - | undefined - user: User | undefined - goToTrackPage: () => void - goToArtistPage: () => void -} - -const nullGuard = withNullGuard( - ({ originalTrack, user, ...p }: RemixesPageProps) => - originalTrack && user && { ...p, originalTrack, user } -) - -const RemixesPage = nullGuard( - ({ title, originalTrack, user, goToTrackPage, goToArtistPage }) => { - useSubPageHeader() - - const { - data, - count, - isFetching, - isPending, - isError, - hasNextPage, - play, - pause, - loadNextPage, - isPlaying, - lineup, - pageSize - } = useRemixesLineup({ - trackId: originalTrack?.track_id, - includeOriginal: true, - includeWinners: true - }) - - const { data: contest } = useRemixContest(originalTrack?.track_id) - const isRemixContest = !!contest - const winnerCount = contest?.eventData?.winners?.length ?? 0 - - const { setHeader } = useContext(HeaderContext) - useEffect(() => { - setHeader( - <> -
- {isRemixContest ? ( - - ) : ( - - )} - - {title} - - - } - /> - - ) - }, [ - setHeader, - title, - originalTrack, - user, - goToArtistPage, - goToTrackPage, - isRemixContest - ]) - - const winnersDelineator = ( - - {messages.winners} - - ) - - const remixesDelineator = ( - - - {messages.remixesTitle} - {count ? ` (${count})` : ''} - - - ) - - const delineatorMap = - winnerCount > 0 - ? { - 0: winnersDelineator, - [winnerCount]: remixesDelineator - } - : { - 0: remixesDelineator - } - - const maxEntries = - count && winnerCount ? count + winnerCount + 1 : undefined - - return ( - - - {messages.originalTrack} - - - - ) - } -) - -export default RemixesPage diff --git a/packages/web/src/pages/remixes-page/components/mobile/RemixesPage.tsx b/packages/web/src/pages/remixes-page/components/mobile/RemixesPage.tsx index 6f6d5e78123..dbdc8fb5652 100644 --- a/packages/web/src/pages/remixes-page/components/mobile/RemixesPage.tsx +++ b/packages/web/src/pages/remixes-page/components/mobile/RemixesPage.tsx @@ -8,14 +8,20 @@ import { useRemixersCount, useRemixesLineup } from '@audius/common/api' -import { remixMessages } from '@audius/common/messages' +import { remixMessages as messages } from '@audius/common/messages' import { - remixesPageLineupActions, remixesPageActions, + remixesPageLineupActions, remixesPageSelectors } from '@audius/common/store' -import { route, pluralize } from '@audius/common/utils' -import { IconRemix as IconRemixes } from '@audius/harmony' +import { route } from '@audius/common/utils' +import { + Flex, + Text, + IconRemix as IconRemixes, + IconTrophy, + FilterButton +} from '@audius/harmony' import { useDispatch, useSelector } from 'react-redux' import { useParams } from 'react-router' @@ -24,7 +30,8 @@ import { HeaderContext } from 'components/header/mobile/HeaderContextProvider' import { TanQueryLineup } from 'components/lineup/TanQueryLineup' import MobilePageContainer from 'components/mobile-page-container/MobilePageContainer' import { useSubPageHeader } from 'components/nav/mobile/NavContext' -import UserBadges from 'components/user-badges/UserBadges' +import { useRemixPageParams } from 'pages/remixes-page/hooks' +import { useUpdateSearchParams } from 'pages/search-page/hooks' import { push as pushRoute } from 'utils/navigation' import { fullTrackRemixesPage } from 'utils/route' @@ -34,14 +41,6 @@ const { profilePage } = route const { getTrackId } = remixesPageSelectors const { fetchTrackSucceeded, reset } = remixesPageActions -const messages = { - remixes: 'Remix', - by: 'by', - of: 'of', - getDescription: (trackName: string, artistName: string) => - `${messages.remixes} ${messages.of} ${trackName} ${messages.by} ${artistName}` -} - type RemixesPageProps = { containerRef?: React.RefObject } @@ -52,7 +51,7 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { const originalTrackId = useSelector(getTrackId) const { data: originalTrack } = useTrack(originalTrackId) const { data: remixContest } = useRemixContest(originalTrackId) - const { data: count = null } = useRemixersCount({ trackId: originalTrackId }) + useRemixersCount({ trackId: originalTrackId }) const { data: originalTrackByPermalink } = useTrackByPermalink( handle && slug ? `/${handle}/${slug}` : null @@ -86,10 +85,16 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { } }, [dispatch, user]) - // All hooks must be called before any early returns useSubPageHeader() + + const updateSortParam = useUpdateSearchParams('sortMethod') + const updateIsCosignParam = useUpdateSearchParams('isCosign') + const updateIsContestEntryParam = useUpdateSearchParams('isContestEntry') + const { sortMethod, isCosign, isContestEntry } = useRemixPageParams() + const { data, + count: lineupCount, isFetching, isPending, isError, @@ -101,14 +106,22 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { lineup, pageSize } = useRemixesLineup({ - trackId: track?.track_id + trackId: track?.track_id, + includeOriginal: true, + includeWinners: true, + sortMethod, + isCosign, + isContestEntry }) + + const { data: contest } = useRemixContest(track?.track_id) const { setHeader } = useContext(HeaderContext) const isRemixContest = !!remixContest const title = isRemixContest - ? remixMessages.submissionsTitle - : remixMessages.remixesTitle + ? messages.submissionsTitle + : messages.remixesTitle + const winnerCount = contest?.eventData?.winners?.length ?? 0 useEffect(() => { if (track && user) { @@ -118,50 +131,96 @@ const RemixesPage = ({ containerRef }: RemixesPageProps) => { className={styles.header} title={ <> - - {title} + {isRemixContest ? ( + + ) : ( + + )} + + {title} + } /> ) } - }, [setHeader, title, track, user, goToArtistPage, goToTrackPage]) + }, [ + setHeader, + title, + track, + user, + goToArtistPage, + goToTrackPage, + isRemixContest + ]) if (!track || !user) { return null } + const winnersDelineator = ( + + {messages.winners} + + ) + + const remixesDelineator = ( + + + {messages.remixesTitle} + {lineupCount ? ` (${lineupCount})` : ''} + + + updateIsCosignParam(isCosign ? '' : 'true')} + /> + {isRemixContest ? ( + + updateIsContestEntryParam(isContestEntry ? '' : 'true') + } + /> + ) : null} + + + + ) + + const delineatorMap = + winnerCount > 0 + ? { + 0: winnersDelineator, + [winnerCount]: remixesDelineator + } + : { + 0: remixesDelineator + } + + const maxEntries = + lineupCount && winnerCount ? lineupCount + winnerCount + 1 : undefined + return ( -
-
- {`${count || ''} ${pluralize( - messages.remixes, - count, - 'es', - !count - )} ${messages.of}`} -
-
- {track.title} -
- {messages.by} -
- {user.name} - -
-
-
+ + {messages.originalTrack} { lineup={lineup} actions={remixesPageLineupActions} pageSize={pageSize} + delineatorMap={delineatorMap} + maxEntries={maxEntries} /> -
+
) }