diff --git a/packages/common/src/hooks/content/types.ts b/packages/common/src/hooks/content/types.ts index b613e3fc232..d4e620f5690 100644 --- a/packages/common/src/hooks/content/types.ts +++ b/packages/common/src/hooks/content/types.ts @@ -1 +1 @@ -export type LockedStatusVariant = 'premium' | 'gated' +export type LockedStatusVariant = 'premium' | 'gated' | 'tokenGated' diff --git a/packages/common/src/hooks/content/useIsTrackUnlockable.ts b/packages/common/src/hooks/content/useIsTrackUnlockable.ts index dfc46ff2879..fffefa150b3 100644 --- a/packages/common/src/hooks/content/useIsTrackUnlockable.ts +++ b/packages/common/src/hooks/content/useIsTrackUnlockable.ts @@ -1,6 +1,7 @@ import { useTrack } from '~/api' import { isContentSpecialAccess, + isContentTokenGated, isContentUSDCPurchaseGated, ID } from '~/models' @@ -14,6 +15,7 @@ export const useIsTrackUnlockable = (trackId: ID) => { const isPurchaseable = isContentUSDCPurchaseGated(streamConditions) const isSpecialAccess = isContentSpecialAccess(streamConditions) + const isTokenGated = isContentTokenGated(streamConditions) - return isPurchaseable || isSpecialAccess + return isPurchaseable || isSpecialAccess || isTokenGated } diff --git a/packages/common/src/hooks/content/useTrackLockedStatusVariant.ts b/packages/common/src/hooks/content/useTrackLockedStatusVariant.ts index 8e87a6e499c..2260b190a53 100644 --- a/packages/common/src/hooks/content/useTrackLockedStatusVariant.ts +++ b/packages/common/src/hooks/content/useTrackLockedStatusVariant.ts @@ -2,6 +2,7 @@ import { useTrack } from '~/api' import { ID, isContentSpecialAccess, + isContentTokenGated, isContentUSDCPurchaseGated } from '~/models' import { Nullable } from '~/utils' @@ -15,12 +16,15 @@ export const useTrackLockedStatusVariant = (trackId: ID) => { const isPurchaseable = isContentUSDCPurchaseGated(streamConditions) const isSpecialAccess = isContentSpecialAccess(streamConditions) + const isTokenGated = isContentTokenGated(streamConditions) let variant: Nullable = null if (isPurchaseable) { variant = 'premium' } else if (isSpecialAccess) { variant = 'gated' + } else if (isTokenGated) { + variant = 'tokenGated' } return variant diff --git a/packages/harmony/src/components/button/Button/Button.tsx b/packages/harmony/src/components/button/Button/Button.tsx index 7b7d4008850..4747730d764 100644 --- a/packages/harmony/src/components/button/Button/Button.tsx +++ b/packages/harmony/src/components/button/Button/Button.tsx @@ -25,6 +25,7 @@ export const Button = forwardRef( hoverColor, variant = 'primary', size = 'default', + rounded, disabled, ...baseProps } = props @@ -176,7 +177,7 @@ export const Button = forwardRef( color: themeColors.static.white, boxSizing: 'border-box', border: 'none', - borderRadius: cornerRadius.s, + borderRadius: rounded ? cornerRadius['2xl'] : cornerRadius.s, boxShadow: shadows.near, ...(isSmallOrXs diff --git a/packages/harmony/src/components/button/Button/types.ts b/packages/harmony/src/components/button/Button/types.ts index de765fda401..4e52a3f1b2b 100644 --- a/packages/harmony/src/components/button/Button/types.ts +++ b/packages/harmony/src/components/button/Button/types.ts @@ -34,4 +34,9 @@ export type ButtonProps = { * The button size */ size?: ButtonSize + + /** + * When true, uses fully rounded (pill-shaped) corners + */ + rounded?: boolean } & Omit diff --git a/packages/mobile/src/components/core/AccessTypeLabel.tsx b/packages/mobile/src/components/core/AccessTypeLabel.tsx index 3fd4c3dcef1..ee05de41b4a 100644 --- a/packages/mobile/src/components/core/AccessTypeLabel.tsx +++ b/packages/mobile/src/components/core/AccessTypeLabel.tsx @@ -13,8 +13,6 @@ import { IconArtistCoin } from '@audius/harmony-native' -import { CoinGatedLabelSvg } from './CoinGatedLabelSvg' - type AccessTypeLabelProps = { type?: AccessType scheduledReleaseDate?: string @@ -59,8 +57,8 @@ const ACCESS_TYPE_CONFIG: Record = { }, [AccessType.TOKEN_GATED]: { icon: IconArtistCoin, - label: 'Coin Gated', - color: 'artistCoin' + label: 'Fan Club', + color: 'subdued' }, [AccessType.EXTRAS]: { icon: IconReceive, @@ -79,14 +77,6 @@ export const AccessTypeLabel = (props: AccessTypeLabelProps) => { ? config.label(scheduledReleaseDate) : config.label - if (type === AccessType.TOKEN_GATED) { - return ( - - - - ) - } - return ( {config.icon ? ( diff --git a/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx b/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx index 442f1626a49..f46ddf560b8 100644 --- a/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx +++ b/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx @@ -24,6 +24,7 @@ import { useDispatch, useSelector } from 'react-redux' import type { FlexProps } from '@audius/harmony-native' import { + Button, Flex, IconLock, IconLockUnlocked, @@ -47,7 +48,7 @@ const { setLockedContentId } = gatedContentActions const messages = { unlocking: 'Unlocking', locked: 'Locked', - buyArtistCoin: 'Buy Fan Club Token', + unlock: 'Unlock', price: (price: string) => `$${price}` } @@ -148,7 +149,7 @@ export const LineupTileAccessStatus = ({ USDC(streamConditions.usdc_purchase.price / 100).toLocaleString() ) : isTokenGated - ? messages.buyArtistCoin + ? messages.unlock : isUnlocking ? messages.unlocking : messages.locked @@ -180,6 +181,20 @@ export const LineupTileAccessStatus = ({ style: { backgroundColor } } + if (isTokenGated && !hasStreamAccess && !isUnlocking) { + return ( + + ) + } + return ( - ) : !isTokenGated ? ( + ) : ( - ) : null} + )} {showButtonText ? ( {buttonText} diff --git a/packages/mobile/src/components/navigation-container/NavigationContainer.tsx b/packages/mobile/src/components/navigation-container/NavigationContainer.tsx index d3b2101be6b..87497d194a0 100644 --- a/packages/mobile/src/components/navigation-container/NavigationContainer.tsx +++ b/packages/mobile/src/components/navigation-container/NavigationContainer.tsx @@ -186,9 +186,6 @@ const NavigationContainer = (props: NavigationContainerProps) => { }, CoinRedeemScreen: { path: 'coins/:ticker/redeem/:code?' - }, - ExclusiveTracksScreen: { - path: 'coins/:ticker/exclusive-tracks' } } }, diff --git a/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx b/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx index 5f1bfaf8e5c..f3569c9fe4c 100644 --- a/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx +++ b/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx @@ -26,14 +26,13 @@ import { makeStyles, flexRowCentered, typography } from 'app/styles' import { spacing } from 'app/styles/spacing' import { useThemeColors } from 'app/utils/theme' -import { CoinGatedLabelSvg } from '../core/CoinGatedLabelSvg' import { TrackImage } from '../image/TrackImage' import { TrackDogEar } from '../track/TrackDogEar' const messages = { specialAccess: 'SPECIAL ACCESS', premiumTrack: 'PREMIUM TRACK', - coinGated: 'COIN GATED', + coinGated: 'FAN CLUB', earn: (amount: string) => `Earn ${amount} $AUDIO for this purchase!` } @@ -142,26 +141,20 @@ export const TrackDetailsTile = ({ {showLabel ? ( - {isTokenGated ? ( - - ) : ( - <> - - - {title} - - - )} + + + {title} + ) : null} { const variant = useTrackLockedStatusVariant(trackId) if (!variant) return null + if (variant === 'tokenGated' && !hasStreamAccess) { + return ( + + + + + {messages.membersOnly} + + + + ) + } + return } diff --git a/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx b/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx index a0ee5e325f7..1bffe49c706 100644 --- a/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx +++ b/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx @@ -23,6 +23,7 @@ export const Button = (props: ButtonProps) => { hexColor, variant = 'primary', size = 'default', + rounded, disabled, style, gradient, @@ -199,7 +200,7 @@ export const Button = (props: ButtonProps) => { const buttonStyles: ViewStyle = { borderWidth: 0, - borderRadius: cornerRadius.s, + borderRadius: rounded ? cornerRadius['2xl'] : cornerRadius.s, alignItems: 'center', justifyContent: 'center', // TODO bring this back properly diff --git a/packages/mobile/src/harmony-native/components/Button/Button/types.ts b/packages/mobile/src/harmony-native/components/Button/Button/types.ts index dac6bc93261..04f97977333 100644 --- a/packages/mobile/src/harmony-native/components/Button/Button/types.ts +++ b/packages/mobile/src/harmony-native/components/Button/Button/types.ts @@ -30,5 +30,10 @@ export type ButtonProps = { */ size?: ButtonSize + /** + * When true, uses fully rounded (pill-shaped) corners + */ + rounded?: boolean + style?: StyleProp } & Omit diff --git a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx index f7bf9825504..870c7c3e88a 100644 --- a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx +++ b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx @@ -32,8 +32,7 @@ import { ChatScreen } from 'app/screens/chat-screen/ChatScreen' import { ChatUserListScreen } from 'app/screens/chat-screen/ChatUserListScreen' import { CoinDetailsScreen, - EditCoinDetailsScreen, - ExclusiveTracksScreen + EditCoinDetailsScreen } from 'app/screens/coin-details-screen' import { CoinRedeemScreen } from 'app/screens/coin-redeem-screen' import { CollectionScreen } from 'app/screens/collection-screen/CollectionScreen' @@ -123,7 +122,6 @@ export type AppTabScreenParamList = { CoinDetailsScreen: { ticker: string } CoinRedeemScreen: { ticker: string; code?: string } EditCoinDetailsScreen: { ticker: string } - ExclusiveTracksScreen: { ticker: string } Upload: { initialMetadata?: Partial } @@ -246,10 +244,6 @@ export const AppTabScreen = ({ baseScreen, Stack }: AppTabScreenProps) => { name='EditCoinDetailsScreen' component={EditCoinDetailsScreen} /> - { - return ( - - - {messages.emptyTitle} - - - {messages.emptyDescription} - - - ) -} - -export const ExclusiveTracksScreen = () => { - const { ticker } = useRoute().params as { ticker: string } - const { data: coin } = useArtistCoinByTicker({ ticker }) - const ownerId = coin?.ownerId - const coinName = coin?.name ?? ticker - - const title = coinName ? `${coinName} Exclusive Tracks` : 'Exclusive Tracks' - - const lineup = useProxySelector((state) => getLineup(state), []) - - const fetchPayload = useMemo(() => ({ userId: ownerId }), [ownerId]) - - if (!ownerId) { - return ( - - - - - - ) - } - - return ( - - - } - showsVerticalScrollIndicator={false} - /> - - - ) -} diff --git a/packages/mobile/src/screens/coin-details-screen/components/ExclusiveTracksCard.tsx b/packages/mobile/src/screens/coin-details-screen/components/ExclusiveTracksCard.tsx deleted file mode 100644 index 5ba71772f39..00000000000 --- a/packages/mobile/src/screens/coin-details-screen/components/ExclusiveTracksCard.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useCallback } from 'react' - -import { - useArtistCoin, - useExclusiveTracks, - useExclusiveTracksCount -} from '@audius/common/api' -import { exclusiveTracksPageLineupActions as exclusiveTracksActions } from '@audius/common/store' - -import { Button, Flex, Text } from '@audius/harmony-native' -import { TanQueryLineup } from 'app/components/lineup/TanQueryLineup' -import { useNavigation } from 'app/hooks/useNavigation' - -const messages = { - exclusiveTracks: 'Exclusive Tracks', - viewAll: 'View All' -} - -const MAX_PREVIEW_TRACKS = 3 - -const itemStyles = { - paddingHorizontal: 0 -} - -type ExclusiveTracksSectionProps = { - mint: string -} - -export const ExclusiveTracksSection = ({ - mint -}: ExclusiveTracksSectionProps) => { - const navigation = useNavigation() - const { data: coin } = useArtistCoin(mint) - const ownerId = coin?.ownerId - const coinName = coin?.name ?? coin?.ticker - - const { data, lineup, pageSize, isFetching, loadNextPage, isPending } = - useExclusiveTracks({ - userId: ownerId, - pageSize: MAX_PREVIEW_TRACKS - }) - - const { data: totalCount = 0 } = useExclusiveTracksCount({ - userId: ownerId - }) - - const handleViewAll = useCallback(() => { - if (coin?.ticker) { - navigation.navigate('ExclusiveTracksScreen', { ticker: coin.ticker }) - } - }, [coin?.ticker, navigation]) - - const shouldShowCard = totalCount > 0 && ownerId - - if (!shouldShowCard) return null - - const title = coinName - ? `${coinName} ${messages.exclusiveTracks}` - : messages.exclusiveTracks - - return ( - - - - - {title} - - {totalCount > 0 ? ( - - ({totalCount}) - - ) : null} - - - - - - ) -} diff --git a/packages/mobile/src/screens/coin-details-screen/components/ExclusiveTracksSection.tsx b/packages/mobile/src/screens/coin-details-screen/components/ExclusiveTracksSection.tsx deleted file mode 100644 index 0f57de69492..00000000000 --- a/packages/mobile/src/screens/coin-details-screen/components/ExclusiveTracksSection.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useCallback } from 'react' - -import { - useArtistCoin, - useExclusiveTracks, - useExclusiveTracksCount -} from '@audius/common/api' -import { exclusiveTracksPageLineupActions as exclusiveTracksActions } from '@audius/common/store' - -import { Flex, PlainButton, Text } from '@audius/harmony-native' -import { TanQueryLineup } from 'app/components/lineup/TanQueryLineup' -import { useNavigation } from 'app/hooks/useNavigation' - -const messages = { - exclusiveTracks: 'Exclusive Tracks', - viewAll: 'View All' -} - -const MAX_PREVIEW_TRACKS = 3 - -const itemStyles = { - paddingHorizontal: 0 -} - -type ExclusiveTracksSectionProps = { - mint: string -} - -export const ExclusiveTracksSection = ({ - mint -}: ExclusiveTracksSectionProps) => { - const navigation = useNavigation() - const { data: coin } = useArtistCoin(mint) - const ownerId = coin?.ownerId - const coinName = coin?.name ?? coin?.ticker - - const { data, lineup, pageSize, isFetching, loadNextPage, isPending } = - useExclusiveTracks({ - userId: ownerId, - pageSize: MAX_PREVIEW_TRACKS - }) - - const { data: totalCount = 0 } = useExclusiveTracksCount({ - userId: ownerId - }) - - const handleViewAll = useCallback(() => { - if (coin?.ticker) { - navigation.navigate('ExclusiveTracksScreen', { ticker: coin.ticker }) - } - }, [coin?.ticker, navigation]) - - const shouldShowCard = totalCount > 0 && ownerId - - if (!shouldShowCard) return null - - const title = coinName - ? `${coinName} ${messages.exclusiveTracks}` - : messages.exclusiveTracks - - return ( - - - - - {title}{' '} - {totalCount > 0 ? ( - ({totalCount}) - ) : null} - - - - {messages.viewAll} - - - - - ) -} diff --git a/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx b/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx index f8104fff461..ff9201adf85 100644 --- a/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx +++ b/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx @@ -44,7 +44,7 @@ const messages = { uploadExclusiveTrack: coinDetailsMessages.coinInfo.uploadExclusiveTrack, becomeAMember: coinDetailsMessages.balance.becomeAMember, hintDescription: coinDetailsMessages.balance.hintDescription, - fanClubFeed: 'Fan Club Feed' + title: 'Fan Club Feed' } const MAX_PREVIEW_TRACKS = 3 @@ -260,7 +260,7 @@ const FanClubFeed = ({ - {messages.fanClubFeed} + {messages.title} @@ -280,7 +280,7 @@ const FanClubFeed = ({ - {messages.fanClubFeed} + {messages.title} {totalCount > 0 ? ( diff --git a/packages/mobile/src/screens/coin-details-screen/index.ts b/packages/mobile/src/screens/coin-details-screen/index.ts index 58fd1c13202..1b88375a13a 100644 --- a/packages/mobile/src/screens/coin-details-screen/index.ts +++ b/packages/mobile/src/screens/coin-details-screen/index.ts @@ -1,3 +1,2 @@ export { CoinDetailsScreen } from './CoinDetailsScreen' export { EditCoinDetailsScreen } from './EditCoinDetailsScreen' -export { ExclusiveTracksScreen } from './ExclusiveTracksScreen' diff --git a/packages/web/src/app/web-player/WebPlayer.tsx b/packages/web/src/app/web-player/WebPlayer.tsx index 2136f043b76..42c8c7b7c04 100644 --- a/packages/web/src/app/web-player/WebPlayer.tsx +++ b/packages/web/src/app/web-player/WebPlayer.tsx @@ -104,23 +104,11 @@ const FanClubDetailPage = lazy(() => default: m.FanClubDetailPage })) ) -const ExclusiveTracksPage = lazy(() => - import('pages/fan-club-detail-page/components/ExclusiveTracksPage').then( - (m) => ({ - default: m.ExclusiveTracksPage - }) - ) -) const ArtistFanClubDetailsPage = lazy(() => import( 'pages/fan-club-detail-page/components/mobile/ArtistFanClubDetailsPage' ).then((m) => ({ default: m.ArtistFanClubDetailsPage })) ) -const MobileExclusiveTracksPage = lazy(() => - import( - 'pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage' - ).then((m) => ({ default: m.ExclusiveTracksPage })) -) const CoinRedeemPage = lazy(() => import('pages/coin-redeem-page/CoinRedeemPage').then((m) => ({ default: m.CoinRedeemPage @@ -408,10 +396,10 @@ const FanClubDetailPageRoute = ({ return } -const CoinExclusiveTracksMobileRoute = () => { +const CoinExclusiveTracksLegacyRedirect = () => { const params = useParams<{ ticker?: string }>() - const { ticker } = params - return + const ticker = (params.ticker ?? '').toUpperCase() + return } type HomePageRedirectProps = { @@ -559,7 +547,7 @@ const WebPlayer = (props: WebPlayerProps) => { useEffect(() => { const client = getClient() - if (client === Client.ELECTRON) { + if (client === Client.ELECTRON && typeof window.require === 'function') { // eslint-disable-next-line @typescript-eslint/no-var-requires ipcRef.current = window.require('electron').ipcRenderer @@ -950,20 +938,13 @@ const WebPlayer = (props: WebPlayerProps) => { } /> } /> - {!isMobile ? ( - } - /> - ) : ( - } - /> - )} + } + /> } + element={} /> { } /> } + element={} + /> + } /> = { }, [AccessType.TOKEN_GATED]: { icon: IconArtistCoin, - label: 'Coin Gated', - color: 'artistCoin' + label: 'Fan Club', + color: 'subdued' }, [AccessType.EXTRAS]: { icon: IconReceive, @@ -77,15 +76,6 @@ export const AccessTypeLabel = (props: AccessTypeLabelProps) => { ? config.label(scheduledReleaseDate) : config.label - // Use coin gated label svg instead of icon and text for token gated - if (type === AccessType.TOKEN_GATED) { - return ( - - - - ) - } - return ( {config.icon ? ( diff --git a/packages/web/src/components/track/GatedConditionsPill.tsx b/packages/web/src/components/track/GatedConditionsPill.tsx index c6417488125..1fd32cc1f21 100644 --- a/packages/web/src/components/track/GatedConditionsPill.tsx +++ b/packages/web/src/components/track/GatedConditionsPill.tsx @@ -17,7 +17,7 @@ import { make, track } from 'services/analytics' const messages = { unlocking: 'Unlocking', locked: 'Locked', - buyArtistCoin: 'Buy Artist Coin' + unlock: 'Unlock' } export const GatedConditionsPill = ({ @@ -50,7 +50,7 @@ export const GatedConditionsPill = ({ message = isPurchase ? USDC(streamConditions.usdc_purchase.price / 100).toLocaleString() : isTokenGated - ? messages.buyArtistCoin + ? messages.unlock : messages.locked } @@ -87,12 +87,28 @@ export const GatedConditionsPill = ({ ] ) + if (isTokenGated) { + return ( + + ) + } + return ( diff --git a/packages/web/src/pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage.module.css b/packages/web/src/pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage.module.css deleted file mode 100644 index a85e8ed07d1..00000000000 --- a/packages/web/src/pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.container { - padding: 0; -} - -.header { - display: flex; - align-items: center; - justify-content: center; - gap: var(--unit-2); - width: 100%; -} - -.tracksContainer { - padding: var(--unit-3); -} diff --git a/packages/web/src/pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage.tsx b/packages/web/src/pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage.tsx deleted file mode 100644 index 00791441075..00000000000 --- a/packages/web/src/pages/fan-club-detail-page/components/mobile/ExclusiveTracksPage.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useContext } from 'react' - -import { useArtistCoinByTicker, useExclusiveTracks } from '@audius/common/api' -import { exclusiveTracksPageLineupActions } from '@audius/common/store' - -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 styles from './ExclusiveTracksPage.module.css' - -const PAGE_SIZE = 100 - -type ExclusiveTracksPageProps = { - ticker: string -} - -export const ExclusiveTracksPage = ({ ticker }: ExclusiveTracksPageProps) => { - useSubPageHeader() - - const { data: coin } = useArtistCoinByTicker({ ticker }) - const ownerId = coin?.ownerId - const coinName = coin?.name ?? ticker - - const title = coinName ? `${coinName} Fan Club Feed` : 'Fan Club Feed' - - const { - data, - isFetching, - isPending, - isError, - hasNextPage, - play, - pause, - loadNextPage, - isPlaying, - lineup, - pageSize - } = useExclusiveTracks({ - userId: ownerId, - pageSize: PAGE_SIZE - }) - - const { setHeader } = useContext(HeaderContext) - useEffect(() => { - setHeader( - <> -
- - ) - }, [setHeader, title]) - - return ( - -
- -
-
- ) -}