Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/common/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export * from './purchaseContent'
export * from './content'
export * from './chats'
export * from './useRemixCountdown'
export * from './useEnterContest'
export * from './useFormattedUSDCBalance'
export * from './useFormattedAudioBalance'
export * from './useFormattedCoinBalance'
Expand Down
50 changes: 50 additions & 0 deletions packages/common/src/hooks/useEnterContest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useCallback } from 'react'

import { useTrack, useUser } from '~/api'
import type { ID } from '~/models'
import type { TrackMetadataForUpload } from '~/store'

type EnterContestArtwork = {
url: string
file: any
}

/**
* Source of truth for the "enter remix contest" upload payload — fetches the
* source track + author and returns a builder that produces the
* `initialMetadata` shape both web and mobile feed into the upload flow.
*
* Each platform fetches its own artwork file (Web `File` vs RN's blob shape
* diverge) and passes it back to `buildInitialMetadata`. Centralising the
* `remix_of` shape — including `user` and the boolean flags — keeps the
* contest entry payload from drifting from the existing remix flow.
*/
export const useEnterContest = (trackId: ID | undefined) => {
const { data: originalTrack } = useTrack(trackId)
const { data: originalUser } = useUser(originalTrack?.owner_id)

const buildInitialMetadata = useCallback(
(
artwork?: EnterContestArtwork
): Partial<TrackMetadataForUpload> | undefined => {
if (!trackId) return undefined
return {
...(artwork ? { artwork } : {}),
genre: originalTrack?.genre ?? '',
remix_of: {
tracks: [
{
parent_track_id: trackId,
user: originalUser,
has_remix_author_reposted: false,
has_remix_author_saved: false
}
]
}
}
},
[trackId, originalUser, originalTrack?.genre]
)

return { originalTrack, originalUser, buildInitialMetadata }
}
43 changes: 43 additions & 0 deletions packages/mobile/src/hooks/useEnterContest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useCallback } from 'react'

import { useEnterContest as useEnterContestShared } from '@audius/common/hooks'
import { type ID, SquareSizes } from '@audius/common/models'

import { useNavigation } from 'app/hooks/useNavigation'

/**
* Returns a "submit a remix to this contest" callback that pushes onto the
* Upload navigator with artwork + genre + remix_of pre-filled. Mobile wrapper
* around the shared common hook — handles the React Native blob/file shape
* (RN's `File` object stores its data on `_data`) and the navigation push,
* while the metadata shape itself comes from `@audius/common/hooks`.
*/
export const useEnterContest = (trackId: ID | undefined) => {
const navigation = useNavigation()
const { originalTrack, buildInitialMetadata } = useEnterContestShared(trackId)

return useCallback(async () => {
if (!trackId) return

let artwork: { url: string; file: any } | undefined
const imageUrl = originalTrack?.artwork?.[SquareSizes.SIZE_480_BY_480] ?? ''
if (imageUrl) {
const response = await fetch(imageUrl)
const blob = await response.blob()
const file = new File([blob], 'image.jpg', { type: blob.type })
artwork = {
url: imageUrl,
file: {
// @ts-ignore: KJ - _data is on the file for some reason
...file._data,
uri: imageUrl
}
}
}

const initialMetadata = buildInitialMetadata(artwork)
if (!initialMetadata) return

navigation.push('Upload', { initialMetadata })
}, [trackId, originalTrack, buildInitialMetadata, navigation])
}
13 changes: 2 additions & 11 deletions packages/mobile/src/screens/contest-screen/ContestScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
collapsibleTabScreen
} from 'app/components/top-tab-bar'
import { UserLink } from 'app/components/user-link'
import { useEnterContest } from 'app/hooks/useEnterContest'
import { useRoute } from 'app/hooks/useRoute'
import { setVisibility } from 'app/store/drawers/slice'

Expand Down Expand Up @@ -294,17 +295,7 @@ export const ContestScreen = () => {
dispatch(setVisibility({ drawer: 'PickWinners', visible: true }))
}, [trackId, dispatch])

const handleEnterContest = useCallback(() => {
if (!trackId)
return // Same wire-up as web: jump into the upload flow with `remix_of`
// pre-filled so the resulting track is linked to this contest's
// parent track. The Upload modal stack reads `initialMetadata` off
// its initial route params and merges it into the track metadata
// when the user picks a file (see SelectTrackScreen).
;(navigation as any).navigate('Upload', {
initialMetadata: { remix_of: { tracks: [{ parent_track_id: trackId }] } }
})
}, [trackId, navigation])
const handleEnterContest = useEnterContest(trackId)

// Hide the stack navigator header — the in-hero back button is the
// only back affordance in the Figma (2888-131647). Leaving the
Expand Down
60 changes: 4 additions & 56 deletions packages/mobile/src/screens/track-screen/UploadRemixFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useCallback } from 'react'
import React from 'react'

import { useUser, useTrack } from '@audius/common/api'
import { SquareSizes, type ID } from '@audius/common/models'
import type { ID } from '@audius/common/models'

import { Button, Flex, IconCloudUpload } from '@audius/harmony-native'
import { useNavigation } from 'app/hooks/useNavigation'
import { useEnterContest } from 'app/hooks/useEnterContest'

const messages = {
contestEnded: 'Contest Ended',
Expand All @@ -20,58 +19,7 @@ type UploadRemixFooterProps = {
* Footer component for uploading remixes in the remix contest section
*/
export const UploadRemixFooter = ({ trackId }: UploadRemixFooterProps) => {
const navigation = useNavigation()
const { data: originalTrack } = useTrack(trackId)
const { data: originalUser } = useUser(originalTrack?.owner_id)

const handlePressSubmitRemix = useCallback(async () => {
if (!trackId) return

let file: File | undefined
const imageUrl = originalTrack?.artwork?.[SquareSizes.SIZE_480_BY_480] ?? ''

if (imageUrl) {
const response = await fetch(imageUrl)
const blob = await response.blob()
file = new File([blob], 'image.jpg', { type: blob.type })
}

const state = {
initialMetadata: {
...(file
? {
artwork: {
url: imageUrl,
file: {
// @ts-ignore: KJ - _data is on the file for some reason
...file._data,
uri: imageUrl
}
}
}
: {}),
genre: originalTrack?.genre ?? '',
remix_of: {
tracks: [
{
parent_track_id: trackId,
user: originalUser,
has_remix_author_reposted: false,
has_remix_author_saved: false
}
]
}
}
}

navigation.push('Upload', state)
}, [
navigation,
originalTrack?.artwork,
originalTrack?.genre,
originalUser,
trackId
])
const handlePressSubmitRemix = useEnterContest(trackId)

return (
<Flex
Expand Down
55 changes: 17 additions & 38 deletions packages/web/src/pages/contest-page/hooks/useEnterContest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useTrack, useUser } from '@audius/common/api'
import { useEnterContest as useEnterContestShared } from '@audius/common/hooks'
import { type ID, SquareSizes } from '@audius/common/models'
import { UPLOAD_PAGE } from '@audius/common/src/utils/route'
import type { TrackMetadataForUpload } from '@audius/common/store'
Expand All @@ -7,58 +7,37 @@ import { useNavigateToPage } from 'hooks/useNavigateToPage'
import { useRequiresAccountCallback } from 'hooks/useRequiresAccount'

/**
* Returns a "submit a remix to this contest" callback that deep-links
* into the upload flow with the source track's artwork + genre +
* remix_of pre-filled. Mirrors the behaviour of
* `RemixContestSection.goToUploadWithRemix` on the desktop track page,
* extracted so contest pages can reuse the same shape.
*
* Why pre-fill artwork + genre: every submission to a remix contest
* shares the contest's source-track context. Forcing the submitter to
* re-upload artwork and re-pick a genre adds friction with no signal
* gained. We default both fields and let the user override before
* publishing.
* Returns a "submit a remix to this contest" callback that deep-links into
* the upload flow with artwork + genre + remix_of pre-filled. Web wrapper
* around the shared common hook — handles the Web `File` blob fetch and the
* navigation + auth gate, while the metadata shape itself comes from
* `@audius/common/hooks`.
*/
export const useEnterContest = (trackId: ID | undefined) => {
const navigate = useNavigateToPage()
const { data: originalTrack } = useTrack(trackId)
const { data: originalUser } = useUser(originalTrack?.owner_id)
const { originalTrack, buildInitialMetadata } = useEnterContestShared(trackId)

return useRequiresAccountCallback(async () => {
if (!trackId) return

let file: File | undefined
let artwork: { url: string; file: File } | undefined
const imageUrl =
originalTrack?.artwork?.[SquareSizes.SIZE_1000_BY_1000] ?? ''
if (imageUrl) {
const response = await fetch(imageUrl)
const blob = await response.blob()
file = new File([blob], 'image.jpg', { type: blob.type })
artwork = {
url: imageUrl,
file: new File([blob], 'image.jpg', { type: blob.type })
}
}

const initialMetadata = buildInitialMetadata(artwork)
if (!initialMetadata) return

const state: { initialMetadata: Partial<TrackMetadataForUpload> } = {
initialMetadata: {
...(file
? {
artwork: {
url: imageUrl,
file
}
}
: {}),
genre: originalTrack?.genre ?? '',
remix_of: {
tracks: [
{
parent_track_id: trackId,
user: originalUser,
has_remix_author_reposted: false,
has_remix_author_saved: false
}
]
}
}
initialMetadata
}
navigate(UPLOAD_PAGE, state)
}, [trackId, navigate, originalTrack, originalUser])
}, [trackId, navigate, originalTrack, buildInitialMetadata])
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { useState, useCallback } from 'react'

import {
useRemixContest,
useRemixesLineup,
useTrack,
useUser
} from '@audius/common/api'
import { ID, Name, SquareSizes } from '@audius/common/models'
import { UPLOAD_PAGE } from '@audius/common/src/utils/route'
import { TrackMetadataForUpload } from '@audius/common/store'
import { useRemixContest, useRemixesLineup, useTrack } from '@audius/common/api'
import { ID, Name } from '@audius/common/models'
import { dayjs } from '@audius/common/utils'
import {
Box,
Expand All @@ -23,8 +16,7 @@ import {
import { Link, useSearchParams } from 'react-router'

import { Tab, TabList } from 'components/tabs'
import { useNavigateToPage } from 'hooks/useNavigateToPage'
import { useRequiresAccountCallback } from 'hooks/useRequiresAccount'
import { useEnterContest } from 'pages/contest-page/hooks/useEnterContest'
import { useUpdateSearchParams } from 'pages/search-page/hooks'
import { track, make } from 'services/analytics'
import { pickWinnersPage } from 'utils/route'
Expand Down Expand Up @@ -64,9 +56,8 @@ export const RemixContestSection = ({
trackId,
isOwner
}: RemixContestSectionProps) => {
const navigate = useNavigateToPage()
const goToUploadWithRemix = useEnterContest(trackId)
const { data: originalTrack } = useTrack(trackId)
const { data: originalUser } = useUser(originalTrack?.owner_id)
const { data: remixContest } = useRemixContest(trackId)
const { data: remixes, count: remixCount = 0 } = useRemixesLineup({
trackId,
Expand Down Expand Up @@ -140,44 +131,6 @@ export const RemixContestSection = ({
}
}, [remixContest?.eventId, trackId])

const goToUploadWithRemix = useRequiresAccountCallback(async () => {
if (!trackId) return

let file: File | undefined
const imageUrl =
originalTrack?.artwork?.[SquareSizes.SIZE_1000_BY_1000] ?? ''

if (imageUrl) {
const response = await fetch(imageUrl)
const blob = await response.blob()
file = new File([blob], 'image.jpg', { type: blob.type })
}

const state: { initialMetadata: Partial<TrackMetadataForUpload> } = {
initialMetadata: {
...(file
? {
artwork: {
url: imageUrl,
file
}
}
: {}),
genre: originalTrack?.genre ?? '',
remix_of: {
tracks: [
{
parent_track_id: trackId,
user: originalUser,
has_remix_author_reposted: false,
has_remix_author_saved: false
}
]
}
}
}
navigate(UPLOAD_PAGE, state)
}, [trackId, navigate, originalTrack, originalUser])
if (!trackId || !remixContest) return null

const totalBoxHeight = TAB_BAR_HEIGHT + contentHeight
Expand Down
Loading