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
28 changes: 26 additions & 2 deletions packages/common/src/api/track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,35 @@ const trackApi = createApi({
kind: Kind.TRACKS,
schemaKey: 'tracks'
}
},
getUserTracksByHandle: {
fetch: async (
{
handle,
currentUserId
}: { handle: string; currentUserId: Nullable<ID> },
{ apiClient }
) => {
return await apiClient.getUserTracksByHandle({
handle,
currentUserId,
getUnlisted: false
})
},
options: {
idListArgKey: 'ids',
kind: Kind.TRACKS,
schemaKey: 'tracks'
}
}
}
})

export const { useGetTrackById, useGetTrackByPermalink, useGetTracksByIds } =
trackApi.hooks
export const {
useGetTrackById,
useGetTrackByPermalink,
useGetTracksByIds,
useGetUserTracksByHandle
} = trackApi.hooks
export const trackApiFetch = trackApi.fetch
export const trackApiReducer = trackApi.reducer
2 changes: 1 addition & 1 deletion packages/common/src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const userApi = createApi({
endpoints: {
getUserById: {
fetch: async (
{ id, currentUserId }: { id: ID; currentUserId: ID },
{ id, currentUserId }: { id: ID; currentUserId: Nullable<ID> },
{ apiClient }
) => {
const apiUser = await apiClient.getUser({ userId: id, currentUserId })
Expand Down
10 changes: 8 additions & 2 deletions packages/web/src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
imageProfilePicEmpty
} from '@audius/common'
import {
Box,
Avatar as HarmonyAvatar,
type AvatarProps as HarmonyAvatarProps
} from '@audius/harmony'
Expand All @@ -28,10 +29,11 @@ const messages = {

type AvatarProps = Omit<HarmonyAvatarProps, 'src'> & {
userId: Maybe<ID>
onClick?: () => void
}

export const Avatar = (props: AvatarProps) => {
const { userId, ...other } = props
const { userId, onClick, ...other } = props
const profileImage = useProfilePicture(
userId ?? null,
SquareSizes.SIZE_150_BY_150
Expand All @@ -52,7 +54,11 @@ export const Avatar = (props: AvatarProps) => {
return user?.user_id === currentUser?.user_id ? messages.your : user?.name
})

return (
return onClick ? (
<Box w='100%' h='100%' onClick={onClick}>
<HarmonyAvatar src={image} {...other} />
</Box>
) : (
<Link to={goTo} aria-label={`${messages.goTo} ${name} ${messages.profile}`}>
<HarmonyAvatar src={image} {...other} />
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HTMLProps } from 'react'
import { HTMLProps, useContext } from 'react'

import { UserMetadata, WidthSizes } from '@audius/common'
import {
Expand All @@ -7,17 +7,22 @@ import {
Flex,
FollowButton,
IconNote,
IconPause,
IconPlay,
IconUser,
IconVerified,
Paper,
Text
} from '@audius/harmony'
import { useField } from 'formik'
import { useHover } from 'react-use'

import { Avatar } from 'components/avatar/Avatar'
import { useMedia } from 'hooks/useMedia'
import { useCoverPhoto } from 'hooks/useUserCoverPhoto'

import { SelectArtistsPreviewContext } from '../utils/selectArtistsPreviewContext'

type FollowArtistTileProps = {
user: UserMetadata
} & HTMLProps<HTMLInputElement>
Expand All @@ -30,6 +35,52 @@ const FollowArtistTile = (props: FollowArtistTileProps) => {
const coverPhoto = useCoverPhoto(user_id, WidthSizes.SIZE_640)
const [followField] = useField({ name: 'selectedArtists', type: 'checkbox' })

const {
togglePreview,
nowPlayingArtistId,
isPlaying: isPreviewPlaying
} = useContext(SelectArtistsPreviewContext)

const isPlaying = isPreviewPlaying && nowPlayingArtistId === user_id

const [avatar] = useHover((isHovered) => (
<Box w={72} h={72} css={{ position: 'absolute', top: 34 }}>
<Flex
h={74}
w={74}
justifyContent='center'
alignItems='center'
css={{
visibility: isHovered || isPlaying ? 'visible' : 'hidden',
pointerEvents: 'none',
position: 'absolute',
top: 0,
left: 0,
borderRadius: 100,
opacity: 0.75,
background:
'radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, 0.50) 0%, rgba(0, 0, 0, 0.10) 100%)',
zIndex: 2
Comment on lines +48 to +63

@DejayJD DejayJD Nov 22, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I wonder if we're taking the inline css a little too far here 😅, this is about ~13 style rules and not just "quick one-off" things. IMO this is still a good use case for a class but maybe thats just me

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Wondering what the meta is here post-harmony. I'm down for either, and please feel free to push changes to this if you see fit

}}
>
{isPlaying ? (
<IconPause size='l' color='staticWhite' />
) : (
<Box pl='xs'>
<IconPlay size='l' color='staticWhite' />
</Box>
)}
</Flex>
<Avatar
variant='strong'
userId={user_id}
onClick={() => {
togglePreview(user_id)
}}
/>
</Box>
))

return (
<Paper
h={220}
Expand All @@ -38,10 +89,7 @@ const FollowArtistTile = (props: FollowArtistTileProps) => {
}}
>
<Flex w='100%' direction='column' alignItems='center'>
<Box w={72} h={72} css={{ position: 'absolute', top: 34 }}>
{/* TODO: play song preview on click */}
<Avatar variant='strong' userId={user_id} />
</Box>
{avatar}
<Box w='100%' h={68} css={{ backgroundImage: `url(${coverPhoto})` }} />
<Flex
direction='column'
Expand Down
43 changes: 25 additions & 18 deletions packages/web/src/pages/sign-up-page/pages/SelectArtistsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { TRENDING_PAGE } from 'utils/route'
import { AccountHeader } from '../components/AccountHeader'
import { ContinueFooter } from '../components/ContinueFooter'
import FollowArtistTile from '../components/FollowArtistTile'
import { SelectArtistsPreviewContextProvider } from '../utils/selectArtistsPreviewContext'

const messages = {
header: 'Follow At Least 3 Artists',
Expand Down Expand Up @@ -204,24 +205,30 @@ export const SelectArtistsPage = () => {
</Flex>
<Form>
<fieldset>
<Paper
css={{
background: 'var(--harmony-bg-default)',
boxShadow: 'none',
minHeight: 500
}}
pv='xl'
ph={isMobile ? 'l' : 'xl'}
gap={isMobile ? 's' : 'm'}
wrap='wrap'
>
{isLoading
? null
: artists?.map((user) => {
const { user_id: userId } = user
return <FollowArtistTile key={userId} user={user} />
})}
</Paper>
<SelectArtistsPreviewContextProvider>
<Paper
css={{
background: 'var(--harmony-bg-default)',
boxShadow: 'none',
minHeight: 500
}}
pv='xl'
ph={isMobile ? 'l' : 'xl'}
gap={isMobile ? 's' : 'm'}
wrap='wrap'
>
{isLoading
? null
: artists?.map((user) => {
return (
<FollowArtistTile
key={user.user_id}
user={user}
/>
)
})}
</Paper>
</SelectArtistsPreviewContextProvider>
</fieldset>
</Form>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { createContext, useCallback, useEffect, useState } from 'react'

import {
ID,
encodeHashId,
useGetUserById,
useGetUserTracksByHandle
} from '@audius/common'
import { useUnmount } from 'react-use'

import { audioPlayer } from 'services/audio-player'
import { apiClient } from 'services/audius-api-client'

type PreviewContextProps = {
isPlaying: boolean
nowPlayingArtistId: number
playPreview: (artistId: ID) => void
stopPreview: () => void
togglePreview: (artistId: ID) => void
}

export const SelectArtistsPreviewContext = createContext<PreviewContextProps>({
isPlaying: false,
nowPlayingArtistId: -1,
playPreview: () => {},
stopPreview: () => {},
togglePreview: () => {}
})

export const SelectArtistsPreviewContextProvider = (props: {
children: JSX.Element
}) => {
const [isPlaying, setIsPlaying] = useState(false)
const [nowPlayingArtistId, setNowPlayingArtistId] = useState<number>(-1)
const [trackId, setTrackId] = useState<string | null>(null)

const { data: artist } = useGetUserById({
id: nowPlayingArtistId,
currentUserId: null
})
const { data: artistTracks } = useGetUserTracksByHandle(
{
handle: artist?.handle,
currentUserId: null
},
{ disabled: !artist?.handle }
)

useEffect(() => {
const trackId = artistTracks?.find((track) => track.is_available)?.track_id
trackId && setTrackId(encodeHashId(trackId))
}, [artistTracks])

const togglePlayback = useCallback(() => {
if (audioPlayer.isPlaying()) {
audioPlayer.pause()
setIsPlaying(false)
} else {
audioPlayer.play()
setIsPlaying(true)
}
}, [])

const stopPreview = useCallback(() => {
audioPlayer.stop()
setNowPlayingArtistId(-1)
setTrackId(null)
setIsPlaying(false)
}, [])

const playPreview = useCallback((artistId: ID) => {
if (audioPlayer.isPlaying()) {
audioPlayer.stop()
}
setNowPlayingArtistId(artistId)
setIsPlaying(true)
}, [])

const togglePreview = useCallback(
(artistId: ID) => {
if (artistId === nowPlayingArtistId) {
togglePlayback()
} else {
audioPlayer.stop()
setIsPlaying(false)
setNowPlayingArtistId(artistId)
}
},
[nowPlayingArtistId, togglePlayback]
)

useEffect(() => {
if (!trackId) return
audioPlayer.load(
0,
stopPreview,
apiClient.makeUrl(`/tracks/${trackId}/stream`)
)
audioPlayer.play()
setIsPlaying(true)
}, [nowPlayingArtistId, stopPreview, trackId])

useUnmount(stopPreview)

return (
<SelectArtistsPreviewContext.Provider
value={{
isPlaying,
nowPlayingArtistId,
playPreview,
stopPreview,
togglePreview
}}
>
{props.children}
</SelectArtistsPreviewContext.Provider>
)
}