Skip to content

Commit 47be06f

Browse files
authored
[PE-6049] Remix contest container for mobile web (#11944)
1 parent 26db394 commit 47be06f

File tree

9 files changed

+407
-47
lines changed

9 files changed

+407
-47
lines changed

packages/web/src/hooks/useTabs/TabStyles.module.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
width: 100%;
2525
}
2626

27+
.tabMobileV2 {
28+
color: var(--harmony-text-subdued);
29+
flex-direction: row;
30+
justify-content: center;
31+
padding: 4px 8px 6px;
32+
}
33+
2734
.tabDesktop {
2835
color: var(--harmony-text-subdued);
2936
flex-direction: row;
@@ -82,6 +89,12 @@
8289
box-shadow: 0 2px 5px -2px var(--tile-shadow-3);
8390
}
8491

92+
.tabBarContainerMobileV2 {
93+
width: 100%;
94+
background-color: var(--harmony-white);
95+
justify-content: space-around;
96+
align-items: center;
97+
}
8598
.bodyContainer {
8699
position: relative;
87100
}

packages/web/src/hooks/useTabs/useTabs.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type TabProps = {
4040
onClick: () => void
4141
isActive: boolean
4242
isMobile: boolean
43+
isMobileV2: boolean
4344
icon?: ReactNode
4445
text: string
4546
label: string
@@ -48,14 +49,15 @@ type TabProps = {
4849

4950
const Tab = forwardRef(
5051
(
51-
{ onClick, icon, text, isActive, isMobile, disabled }: TabProps,
52+
{ onClick, icon, text, isActive, isMobile, isMobileV2, disabled }: TabProps,
5253
ref?: Ref<HTMLDivElement>
5354
) => (
5455
<div
5556
className={cn(
5657
styles.tab,
5758
{ [styles.tabMobile]: isMobile },
58-
{ [styles.tabDesktop]: !isMobile },
59+
{ [styles.tabMobileV2]: isMobileV2 },
60+
{ [styles.tabDesktop]: !isMobile && !isMobileV2 },
5961
{ [styles.tabActive]: isActive },
6062
{ [styles.tabDisabled]: disabled }
6163
)}
@@ -67,6 +69,10 @@ const Tab = forwardRef(
6769
<Text variant='body' size='xs' strength='strong' color='inherit'>
6870
{text}
6971
</Text>
72+
) : isMobileV2 ? (
73+
<Text variant='body' strength='strong' color='inherit'>
74+
{text}
75+
</Text>
7076
) : (
7177
<Text variant='title' color='inherit'>
7278
{text}
@@ -92,6 +98,7 @@ type TabBarProps = {
9298
onClick: (index: number) => void
9399
shouldAnimate: boolean
94100
isMobile: boolean
101+
isMobileV2: boolean
95102
disabledTabTooltipText?: string
96103
// Offset the tab to the left or right.
97104
// offset of 1 offsets 1 tab to the right,
@@ -107,6 +114,7 @@ const TabBar = memo(
107114
tabs,
108115
onClick,
109116
isMobile,
117+
isMobileV2,
110118
disabledTabTooltipText,
111119
fractionalOffset = 0,
112120
pathname
@@ -235,7 +243,8 @@ const TabBar = memo(
235243
className={cn(
236244
styles.tabBarContainer,
237245
{ [styles.tabBarContainerMobile]: isMobile },
238-
{ [styles.tabBarContainerDesktop]: !isMobile }
246+
{ [styles.tabBarContainerMobileV2]: isMobileV2 },
247+
{ [styles.tabBarContainerDesktop]: !isMobile && !isMobileV2 }
239248
)}
240249
role='tablist'
241250
>
@@ -260,6 +269,7 @@ const TabBar = memo(
260269
isActive={isActive}
261270
key={tab.label}
262271
isMobile={isMobile}
272+
isMobileV2={isMobileV2}
263273
disabled={!!tab.disabled}
264274
icon={tab.icon}
265275
label={tab.label}
@@ -380,6 +390,7 @@ type BodyContainerProps = {
380390
lastActive: number
381391
didTransitionCallback: () => void
382392
isMobile: boolean
393+
isMobileV2: boolean
383394
interElementSpacing: number
384395

385396
// If container dimensions are dirty and need a resize.
@@ -921,6 +932,7 @@ type UseTabsArguments = {
921932
bodyClassName?: string
922933
elementClassName?: string
923934
isMobile?: boolean
935+
isMobileV2?: boolean
924936

925937
// Optionally allow useTabs to be a controlled component
926938
selectedTabLabel?: string
@@ -987,6 +999,7 @@ const useTabs = ({
987999
bodyClassName,
9881000
elementClassName,
9891001
isMobile = true,
1002+
isMobileV2 = false,
9901003
interElementSpacing = 0,
9911004
disabledTabTooltipText,
9921005
tabRecalculator,
@@ -1110,6 +1123,7 @@ const useTabs = ({
11101123
tabRecalculator._setRecalculateFunc(recalculateDimensions)
11111124
}
11121125

1126+
// TODO: Add isMobileV2 to the condition and fix the bugs with scrolling
11131127
const BodyContainerElement = isMobile
11141128
? GestureSupportingBodyContainer
11151129
: BodyContainer
@@ -1126,10 +1140,18 @@ const useTabs = ({
11261140
// when the animation finishes. On desktop, it's fired
11271141
// immediately
11281142
setActiveIndex(newIndex)
1129-
!isMobile && onChangeComplete(activeIndex, newIndex)
1143+
!isMobile && !isMobileV2 && onChangeComplete(activeIndex, newIndex)
11301144
onTabClickCb && onTabClickCb(tabs[newIndex].label)
11311145
},
1132-
[isControlled, isMobile, onChangeComplete, activeIndex, onTabClickCb, tabs]
1146+
[
1147+
isControlled,
1148+
isMobile,
1149+
isMobileV2,
1150+
onChangeComplete,
1151+
activeIndex,
1152+
onTabClickCb,
1153+
tabs
1154+
]
11331155
)
11341156

11351157
const tabBarKey = tabs.map((t) => t.label).join('-')
@@ -1143,6 +1165,7 @@ const useTabs = ({
11431165
onClick={onTabClick}
11441166
shouldAnimate={shouldAnimate}
11451167
isMobile={isMobile}
1168+
isMobileV2={isMobileV2}
11461169
disabledTabTooltipText={disabledTabTooltipText}
11471170
fractionalOffset={accentFractionalOffset}
11481171
pathname={pathname}
@@ -1155,6 +1178,7 @@ const useTabs = ({
11551178
onTabClick,
11561179
shouldAnimate,
11571180
isMobile,
1181+
isMobileV2,
11581182
disabledTabTooltipText,
11591183
accentFractionalOffset,
11601184
pathname
@@ -1176,6 +1200,7 @@ const useTabs = ({
11761200
didTransitionCallback || emptyDidTransitionCallback
11771201
}
11781202
isMobile={isMobile}
1203+
isMobileV2={isMobileV2}
11791204
interElementSpacing={interElementSpacing}
11801205
dimensionsAreDirty={dimensionsAreDirty}
11811206
didSetDimensions={didSetDimensions}
@@ -1197,6 +1222,7 @@ const useTabs = ({
11971222
didTransitionCallback,
11981223
emptyDidTransitionCallback,
11991224
isMobile,
1225+
isMobileV2,
12001226
interElementSpacing,
12011227
dimensionsAreDirty,
12021228
didSetDimensions,

packages/web/src/pages/track-page/components/desktop/RemixContestSection.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ export const RemixContestSection = ({
8888
navigate(UPLOAD_PAGE, state)
8989
}, [trackId, navigate])
9090

91-
// TODO: Also return null if no remix contest description
9291
if (!trackId || !remixContest) return null
9392

9493
return (

packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ import {
2424
Box,
2525
Button,
2626
MusicBadge,
27-
Text,
28-
IconCloudUpload
27+
Text
2928
} from '@audius/harmony'
3029
import IconCalendarMonth from '@audius/harmony/src/assets/icons/CalendarMonth.svg'
3130
import IconRobot from '@audius/harmony/src/assets/icons/Robot.svg'
@@ -69,15 +68,7 @@ const messages = {
6968
hidden: 'Hidden',
7069
releases: (releaseDate: string) =>
7170
`Releases ${formatReleaseDate({ date: releaseDate, withHour: true })}`,
72-
remixContest: 'Remix Contest',
73-
contestEnded: 'Contest Ended',
74-
contestDeadline: 'Contest Deadline',
75-
uploadRemixButtonText: 'Upload Remix',
76-
deadline: (deadline?: string) => {
77-
return deadline
78-
? `${dayjs(deadline).format('MM/DD/YYYY')} at ${dayjs(deadline).format('h:mm A')}`
79-
: ''
80-
}
71+
remixContest: 'Remix Contest'
8172
}
8273

8374
type PlayButtonProps = {
@@ -352,35 +343,6 @@ const TrackHeader = ({
352343
setIsDrawerOpen(false)
353344
}, [setIsDrawerOpen])
354345

355-
const handleClick = useCallback(() => {
356-
setIsDrawerOpen(true)
357-
}, [setIsDrawerOpen])
358-
359-
const renderSubmitRemixContestSection = useCallback(() => {
360-
if (!isRemixContest) return null
361-
const isContestOver = dayjs(remixContest.endDate).isBefore(dayjs())
362-
return (
363-
<Flex column gap='m' w='100%'>
364-
<Flex gap='xs' alignItems='center'>
365-
<Text variant='label' color='accent'>
366-
{isContestOver ? messages.contestEnded : messages.contestDeadline}
367-
</Text>
368-
<Text>{messages.deadline(remixContest.endDate)}</Text>
369-
</Flex>
370-
{!isOwner ? (
371-
<Button
372-
variant='secondary'
373-
size='small'
374-
onClick={handleClick}
375-
iconLeft={IconCloudUpload}
376-
>
377-
{messages.uploadRemixButtonText}
378-
</Button>
379-
) : null}
380-
</Flex>
381-
)
382-
}, [isRemixContest, remixContest?.endDate, isOwner, handleClick])
383-
384346
const trendingRank = useTrackRank(trackId)
385347

386348
return (
@@ -506,7 +468,6 @@ const TrackHeader = ({
506468
</Text>
507469
</Flex>
508470
) : null}
509-
{renderSubmitRemixContestSection()}
510471
{hasDownloadableAssets ? (
511472
<Box pt='l' w='100%'>
512473
<Suspense>

packages/web/src/pages/track-page/components/mobile/TrackPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getTrackDefaults } from 'pages/track-page/utils'
2020
import { TrackPageLineup } from '../TrackPageLineup'
2121

2222
import TrackPageHeader from './TrackHeader'
23+
import { RemixContestSection } from './remix-contests/RemixContestSection'
2324

2425
export type OwnProps = {
2526
title: string
@@ -165,6 +166,7 @@ const TrackPage = ({
165166
goToRepostsPage={goToRepostsPage}
166167
/>
167168
</Flex>
169+
<RemixContestSection trackId={defaults.trackId} isOwner={isOwner} />
168170
{isCommentingEnabled ? (
169171
<CommentPreview entityId={defaults.trackId} />
170172
) : null}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { useRemixContest } from '@audius/common/api'
2+
import { ID } from '@audius/common/models'
3+
import { UPLOAD_PAGE } from '@audius/common/src/utils/route'
4+
import { dayjs } from '@audius/common/utils'
5+
import { Button, Divider, Flex, IconCloudUpload, Text } from '@audius/harmony'
6+
7+
import { UserGeneratedText } from 'components/user-generated-text'
8+
import { useNavigateToPage } from 'hooks/useNavigateToPage'
9+
import { useRequiresAccountCallback } from 'hooks/useRequiresAccount'
10+
11+
const messages = {
12+
due: 'Submission Due:',
13+
deadline: (deadline?: string) => {
14+
if (!deadline) return ''
15+
const date = dayjs(deadline)
16+
return `${date.format('ddd. MMM D, YYYY')} at ${date.format('h:mm A')}`
17+
},
18+
ended: 'Contest Ended',
19+
fallbackDescription:
20+
'Enter my remix contest before the deadline for your chance to win!',
21+
uploadRemixButtonText: 'Upload Your Remix'
22+
}
23+
24+
type RemixContestDetailsTabProps = {
25+
trackId: ID
26+
isOwner: boolean
27+
}
28+
29+
/**
30+
* Tab content displaying details about a remix contest
31+
*/
32+
export const RemixContestDetailsTab = ({
33+
trackId,
34+
isOwner
35+
}: RemixContestDetailsTabProps) => {
36+
const navigate = useNavigateToPage()
37+
const { data: remixContest } = useRemixContest(trackId)
38+
const isContestEnded = dayjs(remixContest?.endDate).isBefore(dayjs())
39+
40+
const goToUploadWithRemix = useRequiresAccountCallback(() => {
41+
if (!trackId) return
42+
43+
const state = {
44+
initialMetadata: {
45+
is_remix: true,
46+
remix_of: {
47+
tracks: [{ parent_track_id: trackId }]
48+
}
49+
}
50+
}
51+
navigate(UPLOAD_PAGE, state)
52+
}, [trackId, navigate])
53+
54+
return (
55+
<Flex column w='100%'>
56+
<Flex column gap='s' p='l'>
57+
<Flex row gap='xs'>
58+
<Text variant='title' color='accent'>
59+
{messages.due}
60+
</Text>
61+
<Text variant='body' strength='strong'>
62+
{isContestEnded
63+
? messages.ended
64+
: messages.deadline(remixContest?.endDate)}
65+
</Text>
66+
</Flex>
67+
<UserGeneratedText variant='body'>
68+
{remixContest?.eventData?.description ?? messages.fallbackDescription}
69+
</UserGeneratedText>
70+
</Flex>
71+
{!isOwner ? (
72+
<>
73+
<Divider />
74+
<Flex p='l' pb='s'>
75+
<Button
76+
variant='secondary'
77+
size='small'
78+
fullWidth
79+
onClick={goToUploadWithRemix}
80+
iconLeft={IconCloudUpload}
81+
>
82+
{messages.uploadRemixButtonText}
83+
</Button>
84+
</Flex>
85+
</>
86+
) : null}
87+
</Flex>
88+
)
89+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useRemixContest } from '@audius/common/api'
2+
import { ID } from '@audius/common/models'
3+
import { Flex } from '@audius/harmony'
4+
5+
import { UserGeneratedText } from 'components/user-generated-text'
6+
7+
type RemixContestPrizesTabProps = {
8+
trackId: ID
9+
}
10+
11+
export const RemixContestPrizesTab = ({
12+
trackId
13+
}: RemixContestPrizesTabProps) => {
14+
const { data: remixContest } = useRemixContest(trackId)
15+
16+
return (
17+
<Flex column gap='s' p='l' pb='s'>
18+
<UserGeneratedText variant='body'>
19+
{remixContest?.eventData?.prizeInfo}
20+
</UserGeneratedText>
21+
</Flex>
22+
)
23+
}

0 commit comments

Comments
 (0)