diff --git a/packages/common/src/utils/wallet.ts b/packages/common/src/utils/wallet.ts
index fc691972d9..57c11795b9 100644
--- a/packages/common/src/utils/wallet.ts
+++ b/packages/common/src/utils/wallet.ts
@@ -1,7 +1,14 @@
import BN from 'bn.js'
import JSBI from 'jsbi'
-import { BNAudio, BNWei, StringAudio, StringWei } from 'models/Wallet'
+import {
+ BNAudio,
+ BNUSDC,
+ BNWei,
+ StringAudio,
+ StringUSDC,
+ StringWei
+} from 'models/Wallet'
import {
WEI,
trimRightZeros,
@@ -12,6 +19,7 @@ import {
} from 'utils/formatUtil'
import { Nullable } from 'utils/typeUtils'
+/** AUDIO utils */
const WEI_DECIMALS = 18 // 18 decimals on ETH AUDIO
const SPL_DECIMALS = 8 // 8 decimals on SPL AUDIO
@@ -102,14 +110,6 @@ export const formatWei = (
return formatNumberCommas(trimmed) as StringAudio
}
-export const shortenSPLAddress = (addr: string) => {
- return `${addr.substring(0, 4)}...${addr.substr(addr.length - 5)}`
-}
-
-export const shortenEthAddress = (addr: string) => {
- return `0x${addr.substring(2, 4)}...${addr.substr(addr.length - 5)}`
-}
-
export const convertJSBIToAmountObject = (amount: JSBI, decimals: number) => {
const divisor = JSBI.BigInt(10 ** decimals)
const quotient = JSBI.divide(amount, divisor)
@@ -134,3 +134,39 @@ export const convertWeiToWAudio = (amount: BN) => {
const decimals = WEI_DECIMALS - SPL_DECIMALS
return amount.div(new BN('1'.padEnd(decimals + 1, '0')))
}
+
+/** USDC Utils */
+export const BN_USDC_WEI = new BN('1000000')
+export const BN_USDC_CENT_WEI = new BN('10000')
+const BN_USDC_WEI_ROUNDING_FRACTION = new BN('9999')
+
+/** Round a USDC value as a BN up to the nearest cent and return as a BN */
+export const ceilingBNUSDCToNearestCent = (value: BNUSDC): BNUSDC => {
+ return value
+ .add(BN_USDC_WEI_ROUNDING_FRACTION)
+ .div(BN_USDC_CENT_WEI)
+ .mul(BN_USDC_CENT_WEI) as BNUSDC
+}
+
+/** Formats a USDC wei string (full precision) to a fixed string suitable for
+display as a dollar amount. Note: will lose precision by rounding _up_ to nearest cent */
+export const formatUSDCWeiToUSDString = (amount: StringUSDC, precision = 2) => {
+ // Since we only need two digits of precision, we will multiply up by 1000
+ // with BN, divide by $1 Wei, ceiling up to the nearest cent,
+ // and then convert to JS number and divide back down before formatting to
+ // two decimal places.
+ const cents =
+ ceilingBNUSDCToNearestCent(new BN(amount) as BNUSDC)
+ .div(BN_USDC_CENT_WEI)
+ .toNumber() / 100
+ return formatNumberCommas(cents.toFixed(precision))
+}
+
+/** General Wallet Utils */
+export const shortenSPLAddress = (addr: string) => {
+ return `${addr.substring(0, 4)}...${addr.substr(addr.length - 5)}`
+}
+
+export const shortenEthAddress = (addr: string) => {
+ return `0x${addr.substring(2, 4)}...${addr.substr(addr.length - 5)}`
+}
diff --git a/packages/stems/src/assets/icons/iconCart.svg b/packages/stems/src/assets/icons/iconCart.svg
new file mode 100644
index 0000000000..79504308b1
--- /dev/null
+++ b/packages/stems/src/assets/icons/iconCart.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/stems/src/assets/styles/colors.css b/packages/stems/src/assets/styles/colors.css
index 8384c6cd7f..5b4aaf3402 100644
--- a/packages/stems/src/assets/styles/colors.css
+++ b/packages/stems/src/assets/styles/colors.css
@@ -62,6 +62,8 @@ root {
--accent-blue: #1ba1f1;
--accent-purple: #8e51cf;
+ --special-light-green: #13c65a;
+
--primary-gradient: linear-gradient(135deg, #ee00ed 0%, #c514e0 100%);
--secondary-gradient: linear-gradient(315deg, #6b0fb3 0%, #7e1bcc 100%);
--page-header-gradient-color-1: #5b23e1;
diff --git a/packages/stems/src/components/Icons/index.ts b/packages/stems/src/components/Icons/index.ts
index bb687848af..7ca2b1af98 100644
--- a/packages/stems/src/components/Icons/index.ts
+++ b/packages/stems/src/components/Icons/index.ts
@@ -16,6 +16,7 @@ export { ReactComponent as IconBigSearch } from '../../assets/icons/iconBigSearc
export { ReactComponent as IconCalendar } from '../../assets/icons/iconCalendar.svg'
export { ReactComponent as IconCamera } from '../../assets/icons/iconCamera.svg'
export { ReactComponent as IconCareers } from '../../assets/icons/iconCareers.svg'
+export { ReactComponent as IconCart } from '../../assets/icons/iconCart.svg'
export { ReactComponent as IconCaretDown } from '../../assets/icons/iconCaretDown.svg'
export { ReactComponent as IconCaretRight } from '../../assets/icons/iconCaretRight.svg'
export { ReactComponent as IconCheck } from '../../assets/icons/iconCheck.svg'
diff --git a/packages/stems/src/styles/colors.ts b/packages/stems/src/styles/colors.ts
index 5b83514f1d..d64553fa09 100644
--- a/packages/stems/src/styles/colors.ts
+++ b/packages/stems/src/styles/colors.ts
@@ -37,3 +37,4 @@ export type ColorValue =
| 'accentOrangeLight1'
| 'accentPurple'
| 'accentBlue'
+ | 'specialLightGreen'
diff --git a/packages/web/src/assets/styles/colors.css b/packages/web/src/assets/styles/colors.css
index 92b0753938..beed8b1d86 100644
--- a/packages/web/src/assets/styles/colors.css
+++ b/packages/web/src/assets/styles/colors.css
@@ -59,6 +59,8 @@
--accent-purple: #8e51cf;
+ --special-light-green: #13c65a;
+
--ai-primary: #1fd187;
--ai-secondary: #0fc578;
diff --git a/packages/web/src/components/dog-ear/DogEar.module.css b/packages/web/src/components/dog-ear/DogEar.module.css
index 007c4eab41..6679d6818e 100644
--- a/packages/web/src/components/dog-ear/DogEar.module.css
+++ b/packages/web/src/components/dog-ear/DogEar.module.css
@@ -20,6 +20,10 @@
background: var(--accent-blue);
}
+.purchase {
+ background: var(--special-light-green);
+}
+
.star {
background: linear-gradient(314.61deg, #7e1bcc 0%, #a22feb 100%);
}
diff --git a/packages/web/src/components/dog-ear/DogEar.tsx b/packages/web/src/components/dog-ear/DogEar.tsx
index d4415b6e23..684295c966 100644
--- a/packages/web/src/components/dog-ear/DogEar.tsx
+++ b/packages/web/src/components/dog-ear/DogEar.tsx
@@ -1,5 +1,10 @@
import { DogEarType } from '@audius/common'
-import { IconCollectible, IconLock, IconSpecialAccess } from '@audius/stems'
+import {
+ IconCart,
+ IconCollectible,
+ IconLock,
+ IconSpecialAccess
+} from '@audius/stems'
import cn from 'classnames'
import { ReactComponent as IconHidden } from 'assets/img/iconHidden.svg'
@@ -29,6 +34,8 @@ export const DogEar = (props: DogEarProps) => {
return
case DogEarType.COLLECTIBLE_GATED:
return
+ case DogEarType.USDC_PURCHASE:
+ return
case DogEarType.SPECIAL_ACCESS:
return
}
@@ -48,6 +55,7 @@ export const DogEar = (props: DogEarProps) => {
DogEarType.SPECIAL_ACCESS,
DogEarType.LOCKED
].includes(type),
+ [styles.purchase]: type === DogEarType.USDC_PURCHASE,
[styles.star]: type === DogEarType.STAR,
[styles.hidden]: type === DogEarType.HIDDEN
})}
diff --git a/packages/web/src/components/track/LockedStatusBadge.module.css b/packages/web/src/components/track/LockedStatusBadge.module.css
index 72ef278a0f..dbebae4248 100644
--- a/packages/web/src/components/track/LockedStatusBadge.module.css
+++ b/packages/web/src/components/track/LockedStatusBadge.module.css
@@ -6,10 +6,15 @@
height: var(--unit-4);
padding: var(--unit-half) var(--unit-2);
}
-.unlocked {
+
+.unlocked.gated {
background-color: var(--accent-blue);
}
+.unlocked.premium {
+ background-color: var(--special-light-green);
+}
+
.icon {
width: 14px;
height: 14px;
diff --git a/packages/web/src/components/track/LockedStatusBadge.tsx b/packages/web/src/components/track/LockedStatusBadge.tsx
index 497503511c..364100d377 100644
--- a/packages/web/src/components/track/LockedStatusBadge.tsx
+++ b/packages/web/src/components/track/LockedStatusBadge.tsx
@@ -5,14 +5,22 @@ import styles from './LockedStatusBadge.module.css'
export type LockedStatusBadgeProps = {
locked: boolean
+ variant: 'premium' | 'gated'
}
/** Renders a small badge with locked or unlocked icon */
-export const LockedStatusBadge = ({ locked }: LockedStatusBadgeProps) => {
+export const LockedStatusBadge = ({
+ locked,
+ variant
+}: LockedStatusBadgeProps) => {
const LockComponent = locked ? IconLock : IconLockUnlocked
return (
diff --git a/packages/web/src/components/track/PremiumConditionsPill.module.css b/packages/web/src/components/track/PremiumConditionsPill.module.css
new file mode 100644
index 0000000000..2653cb1e62
--- /dev/null
+++ b/packages/web/src/components/track/PremiumConditionsPill.module.css
@@ -0,0 +1,38 @@
+.container {
+ align-items: center;
+ background-color: var(--accent-blue);
+ border-radius: var(--unit-1);
+ color: var(--static-white);
+ display: flex;
+ gap: var(--unit-1);
+ justify-content: center;
+ height: var(--unit-6);
+ min-width: var(--unit-16);
+ padding: var(--unit-2) var(--unit-3);
+}
+
+.container svg {
+ width: var(--unit-4);
+ height: var(--unit-4);
+}
+
+.container path {
+ fill: var(--static-white);
+}
+
+.gatedContent {
+ background-color: var(--accent-blue);
+}
+
+.premiumContent {
+ background-color: var(--special-light-green);
+}
+
+.spinner {
+ height: var(--unit-4);
+ width: var(--unit-4);
+}
+
+.spinner g path {
+ stroke: var(--static-white);
+}
diff --git a/packages/web/src/components/track/PremiumConditionsPill.tsx b/packages/web/src/components/track/PremiumConditionsPill.tsx
new file mode 100644
index 0000000000..e6ad37e6a3
--- /dev/null
+++ b/packages/web/src/components/track/PremiumConditionsPill.tsx
@@ -0,0 +1,50 @@
+import {
+ PremiumConditions,
+ formatUSDCWeiToUSDString,
+ isPremiumContentUSDCPurchaseGated
+} from '@audius/common'
+import { IconLock } from '@audius/stems'
+import cn from 'classnames'
+
+import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'
+
+import styles from './PremiumConditionsPill.module.css'
+
+const messages = {
+ unlocking: 'Unlocking',
+ locked: 'Locked'
+}
+
+export const PremiumConditionsPill = ({
+ premiumConditions,
+ unlocking
+}: {
+ premiumConditions: PremiumConditions
+ unlocking: boolean
+}) => {
+ const isPurchase = isPremiumContentUSDCPurchaseGated(premiumConditions)
+ const icon = unlocking ? (
+
+ ) : isPurchase ? null : (
+
+ )
+
+ let message = null
+ if (unlocking) {
+ // Show only spinner when unlocking a purchase
+ message = isPurchase ? null : messages.unlocking
+ } else {
+ message = isPurchase
+ ? `$${formatUSDCWeiToUSDString(premiumConditions.usdc_purchase.price)}`
+ : messages.locked
+ }
+
+ const colorStyle = isPurchase ? styles.premiumContent : styles.gatedContent
+
+ return (
+
+ {icon}
+ {message}
+
+ )
+}
diff --git a/packages/web/src/components/track/PremiumContentLabel.module.css b/packages/web/src/components/track/PremiumContentLabel.module.css
index 4dacffcd69..45c43a2fac 100644
--- a/packages/web/src/components/track/PremiumContentLabel.module.css
+++ b/packages/web/src/components/track/PremiumContentLabel.module.css
@@ -21,3 +21,11 @@
.gatedContent .icon path {
fill: var(--accent-blue);
}
+
+.premiumContent {
+ color: var(--special-light-green);
+}
+
+.premiumContent .icon path {
+ fill: var(--special-light-green);
+}
diff --git a/packages/web/src/components/track/PremiumContentLabel.tsx b/packages/web/src/components/track/PremiumContentLabel.tsx
index 805bc8dd0a..7cd9f292dd 100644
--- a/packages/web/src/components/track/PremiumContentLabel.tsx
+++ b/packages/web/src/components/track/PremiumContentLabel.tsx
@@ -1,16 +1,18 @@
import {
PremiumConditions,
Nullable,
- isPremiumContentCollectibleGated
+ isPremiumContentCollectibleGated,
+ isPremiumContentUSDCPurchaseGated
} from '@audius/common'
-import { IconCollectible, IconSpecialAccess } from '@audius/stems'
+import { IconCart, IconCollectible, IconSpecialAccess } from '@audius/stems'
import cn from 'classnames'
import styles from './PremiumContentLabel.module.css'
const messages = {
collectibleGated: 'Collectible Gated',
- specialAccess: 'Special Access'
+ specialAccess: 'Special Access',
+ premium: 'Premium'
}
/** Renders a label indicating a premium content type. If the user does
@@ -28,12 +30,17 @@ export const PremiumContentLabel = ({
const showColor = isOwner || !doesUserHaveAccess
let message = messages.specialAccess
let IconComponent = IconSpecialAccess
- const colorStyle = styles.gatedContent
+ let colorStyle = styles.gatedContent
if (isPremiumContentCollectibleGated(premiumConditions)) {
message = messages.collectibleGated
IconComponent = IconCollectible
}
+ if (isPremiumContentUSDCPurchaseGated(premiumConditions)) {
+ message = messages.premium
+ IconComponent = IconCart
+ colorStyle = styles.premiumContent
+ }
return (
diff --git a/packages/web/src/components/track/PremiumTrackSection.tsx b/packages/web/src/components/track/PremiumTrackSection.tsx
index bed838862f..c59ea35a6e 100644
--- a/packages/web/src/components/track/PremiumTrackSection.tsx
+++ b/packages/web/src/components/track/PremiumTrackSection.tsx
@@ -15,7 +15,8 @@ import {
removeNullable,
isPremiumContentFollowGated,
isPremiumContentCollectibleGated,
- isPremiumContentTipGated
+ isPremiumContentTipGated,
+ isPremiumContentUSDCPurchaseGated
} from '@audius/common'
import {
Button,
@@ -303,7 +304,14 @@ const LockedPremiumTrackSection = ({
styles.premiumContentSectionTitle
)}
>
-
+
{messages.howToUnlock}
{renderLockedDescription()}
@@ -484,7 +492,14 @@ const UnlockedPremiumTrackSection = ({
)
) : (
-
+
)}
{isOwner
? isPremiumContentCollectibleGated(premiumConditions)
diff --git a/packages/web/src/components/track/desktop/BottomRow.tsx b/packages/web/src/components/track/desktop/BottomRow.tsx
index 4c16baf729..96725bd34e 100644
--- a/packages/web/src/components/track/desktop/BottomRow.tsx
+++ b/packages/web/src/components/track/desktop/BottomRow.tsx
@@ -1,17 +1,23 @@
import { ReactNode, useCallback } from 'react'
-import { FieldVisibility, premiumContentSelectors, ID } from '@audius/common'
-import { IconLock } from '@audius/stems'
+import {
+ FieldVisibility,
+ premiumContentSelectors,
+ ID,
+ PremiumConditions,
+ Nullable
+} from '@audius/common'
import cn from 'classnames'
import { useSelector } from 'react-redux'
import FavoriteButton from 'components/alt-button/FavoriteButton'
import RepostButton from 'components/alt-button/RepostButton'
import ShareButton from 'components/alt-button/ShareButton'
-import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'
import Tooltip from 'components/tooltip/Tooltip'
import typeStyles from 'components/typography/typography.module.css'
+import { PremiumConditionsPill } from '../PremiumConditionsPill'
+
import styles from './TrackTile.module.css'
const { getPremiumTrackStatusMap } = premiumContentSelectors
@@ -39,6 +45,7 @@ type BottomRowProps = {
showIconButtons?: boolean
isTrack?: boolean
trackId?: ID
+ premiumConditions?: Nullable
onClickRepost: (e?: any) => void
onClickFavorite: (e?: any) => void
onClickShare: (e?: any) => void
@@ -60,6 +67,7 @@ export const BottomRow = ({
showIconButtons,
isTrack,
trackId,
+ premiumConditions,
onClickRepost,
onClickFavorite,
onClickShare
@@ -101,21 +109,14 @@ export const BottomRow = ({
)
}
- if (isTrack && !isLoading && !doesUserHaveAccess) {
+ if (isTrack && premiumConditions && !isLoading && !doesUserHaveAccess) {
return (
- {premiumTrackStatus === 'UNLOCKING' ? (
-
-
- {messages.unlocking}
-
- ) : (
-
-
- {messages.locked}
-
- )}
- {!isLoading ?
{rightActions}
: null}
+
+
{rightActions}
)
}
diff --git a/packages/web/src/components/track/desktop/TrackTile.module.css b/packages/web/src/components/track/desktop/TrackTile.module.css
index 5f354df29c..38b663f559 100644
--- a/packages/web/src/components/track/desktop/TrackTile.module.css
+++ b/packages/web/src/components/track/desktop/TrackTile.module.css
@@ -366,35 +366,6 @@
width: 100%;
}
-.bottomRow .premiumContent {
- align-items: center;
- background-color: var(--accent-blue);
- border-radius: var(--unit-1);
- color: var(--static-white);
- display: flex;
- gap: var(--unit-1);
- height: var(--unit-6);
- padding: var(--unit-2) var(--unit-3);
-}
-
-.bottomRow .premiumContent svg {
- width: var(--unit-4);
- height: var(--unit-4);
-}
-
-.bottomRow .premiumContent path {
- fill: var(--static-white);
-}
-
-.spinner {
- height: var(--unit-4);
- width: var(--unit-4);
-}
-
-.spinner g path {
- stroke: var(--static-white);
-}
-
.bottomRow .iconButtons {
display: flex;
gap: var(--unit-8);
diff --git a/packages/web/src/components/track/desktop/TrackTile.tsx b/packages/web/src/components/track/desktop/TrackTile.tsx
index cc9ece9d92..028760e43c 100644
--- a/packages/web/src/components/track/desktop/TrackTile.tsx
+++ b/packages/web/src/components/track/desktop/TrackTile.tsx
@@ -8,7 +8,8 @@ import {
formatLineupTileDuration,
Genre,
CommonState,
- getDogEarType
+ getDogEarType,
+ isPremiumContentUSDCPurchaseGated
} from '@audius/common'
import { IconCheck, IconCrown, IconHidden, ProgressBar } from '@audius/stems'
import cn from 'classnames'
@@ -21,7 +22,7 @@ import Skeleton from 'components/skeleton/Skeleton'
import typeStyles from 'components/typography/typography.module.css'
import { useFlag } from 'hooks/useRemoteConfig'
-import { LockedStatusBadge } from '../LockedStatusBadge'
+import { LockedStatusBadge, LockedStatusBadgeProps } from '../LockedStatusBadge'
import { PremiumContentLabel } from '../PremiumContentLabel'
import { messages } from '../trackTileMessages'
import {
@@ -67,7 +68,8 @@ const renderLockedOrPlaysContent = ({
fieldVisibility,
isOwner,
isPremium,
- listenCount
+ listenCount,
+ variant
}: Pick<
TrackTileProps,
| 'doesUserHaveAccess'
@@ -75,9 +77,10 @@ const renderLockedOrPlaysContent = ({
| 'isOwner'
| 'isPremium'
| 'listenCount'
->) => {
+> &
+ Pick) => {
if (isPremium && !isOwner) {
- return
+ return
}
const hidePlays = fieldVisibility
@@ -153,6 +156,8 @@ const TrackTile = ({
const isLongFormContent =
genre === Genre.PODCASTS || genre === Genre.AUDIOBOOKS
+ const isPurchase = isPremiumContentUSDCPurchaseGated(premiumConditions)
+
const getDurationText = () => {
if (!duration) {
return ''
@@ -332,7 +337,8 @@ const TrackTile = ({
fieldVisibility,
isOwner,
isPremium,
- listenCount
+ listenCount,
+ variant: isPurchase ? 'premium' : 'gated'
})
: null}
@@ -355,6 +361,7 @@ const TrackTile = ({
onClickRepost={onClickRepost}
onClickFavorite={onClickFavorite}
onClickShare={onClickShare}
+ premiumConditions={premiumConditions}
isTrack={isTrack}
trackId={trackId}
/>
diff --git a/packages/web/src/components/track/mobile/BottomButtons.tsx b/packages/web/src/components/track/mobile/BottomButtons.tsx
index a7c3a13d69..355738a79c 100644
--- a/packages/web/src/components/track/mobile/BottomButtons.tsx
+++ b/packages/web/src/components/track/mobile/BottomButtons.tsx
@@ -1,16 +1,16 @@
import { memo } from 'react'
-import { PremiumTrackStatus } from '@audius/common'
-import { IconLock } from '@audius/stems'
+import { Nullable, PremiumConditions, PremiumTrackStatus } from '@audius/common'
import cn from 'classnames'
import FavoriteButton from 'components/alt-button/FavoriteButton'
import MoreButton from 'components/alt-button/MoreButton'
import RepostButton from 'components/alt-button/RepostButton'
import ShareButton from 'components/alt-button/ShareButton'
-import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'
import typeStyles from 'components/typography/typography.module.css'
+import { PremiumConditionsPill } from '../PremiumConditionsPill'
+
import styles from './BottomButtons.module.css'
type BottomButtonsProps = {
@@ -20,21 +20,18 @@ type BottomButtonsProps = {
toggleRepost: () => void
onClickOverflow: () => void
onShare: () => void
+ isLoading: boolean
isOwner: boolean
isDarkMode: boolean
isUnlisted?: boolean
isShareHidden?: boolean
isTrack?: boolean
doesUserHaveAccess?: boolean
+ premiumConditions?: Nullable
premiumTrackStatus?: PremiumTrackStatus
isMatrixMode: boolean
}
-const messages = {
- locked: 'LOCKED',
- unlocking: 'UNLOCKING'
-}
-
const BottomButtons = (props: BottomButtonsProps) => {
const moreButton = (
{
)
// Premium condition without access
- if (props.isTrack && !props.doesUserHaveAccess) {
+ if (
+ props.isTrack &&
+ !props.isLoading &&
+ props.premiumConditions &&
+ !props.doesUserHaveAccess
+ ) {
return (
- {props.premiumTrackStatus === 'UNLOCKING' ? (
-
-
- {messages.unlocking}
-
- ) : (
-
-
- {messages.locked}
-
- )}
+
{moreButton}
diff --git a/packages/web/src/components/track/mobile/PlaylistTile.tsx b/packages/web/src/components/track/mobile/PlaylistTile.tsx
index bab1298d96..2f76ba11a4 100644
--- a/packages/web/src/components/track/mobile/PlaylistTile.tsx
+++ b/packages/web/src/components/track/mobile/PlaylistTile.tsx
@@ -278,6 +278,7 @@ const PlaylistTile = (props: PlaylistTileProps & ExtraProps) => {
toggleRepost={props.toggleRepost}
onShare={props.onShare}
onClickOverflow={props.onClickOverflow}
+ isLoading={props.isLoading}
isOwner={props.isOwner}
isDarkMode={props.darkMode}
isMatrixMode={props.isMatrix}
diff --git a/packages/web/src/components/track/mobile/TrackTile.tsx b/packages/web/src/components/track/mobile/TrackTile.tsx
index f1c6d731ef..1ec11675c8 100644
--- a/packages/web/src/components/track/mobile/TrackTile.tsx
+++ b/packages/web/src/components/track/mobile/TrackTile.tsx
@@ -9,7 +9,8 @@ import {
premiumContentActions,
formatLineupTileDuration,
Genre,
- getDogEarType
+ getDogEarType,
+ isPremiumContentUSDCPurchaseGated
} from '@audius/common'
import { IconCrown, IconHidden, IconTrending } from '@audius/stems'
import cn from 'classnames'
@@ -29,7 +30,7 @@ import typeStyles from 'components/typography/typography.module.css'
import UserBadges from 'components/user-badges/UserBadges'
import { profilePage } from 'utils/route'
-import { LockedStatusBadge } from '../LockedStatusBadge'
+import { LockedStatusBadge, LockedStatusBadgeProps } from '../LockedStatusBadge'
import { messages } from '../trackTileMessages'
import BottomButtons from './BottomButtons'
@@ -52,7 +53,7 @@ type ExtraProps = {
darkMode: boolean
isMatrix: boolean
isPremium: boolean
- premiumConditions: Nullable
+ premiumConditions?: Nullable
doesUserHaveAccess: boolean
}
@@ -63,7 +64,8 @@ const renderLockedOrPlaysContent = ({
fieldVisibility,
isOwner,
isPremium,
- listenCount
+ listenCount,
+ variant
}: Pick<
CombinedProps,
| 'doesUserHaveAccess'
@@ -71,9 +73,10 @@ const renderLockedOrPlaysContent = ({
| 'isOwner'
| 'isPremium'
| 'listenCount'
->) => {
+> &
+ Pick) => {
if (isPremium && !isOwner) {
- return
+ return
}
const hidePlays = fieldVisibility
@@ -178,6 +181,7 @@ const TrackTile = (props: CombinedProps) => {
const premiumTrackStatus = trackId
? premiumTrackStatusMap[trackId]
: undefined
+ const isPurchase = isPremiumContentUSDCPurchaseGated(premiumConditions)
const DogEarIconType = isLoading
? undefined
@@ -425,7 +429,8 @@ const TrackTile = (props: CombinedProps) => {
fieldVisibility,
isOwner,
isPremium,
- listenCount
+ listenCount,
+ variant: isPurchase ? 'premium' : 'gated'
})
: null}
@@ -439,8 +444,10 @@ const TrackTile = (props: CombinedProps) => {
onShare={onClickShare}
onClickOverflow={onClickOverflowMenu}
isOwner={isOwner}
+ isLoading={isLoading}
isUnlisted={isUnlisted}
doesUserHaveAccess={doesUserHaveAccess}
+ premiumConditions={premiumConditions}
premiumTrackStatus={premiumTrackStatus}
isShareHidden={hideShare}
isDarkMode={darkMode}
diff --git a/packages/web/src/utils/theme/dark.ts b/packages/web/src/utils/theme/dark.ts
index 159818a6b6..94bc3a0f63 100644
--- a/packages/web/src/utils/theme/dark.ts
+++ b/packages/web/src/utils/theme/dark.ts
@@ -35,6 +35,8 @@ const theme = {
'--accent-red-dark-1': '#C43047',
+ '--special-light-green': '#13c65a',
+
/* Semantic text */
'--text-default': 'var(--neutral)',
'--text-subdued': 'var(--neutral-light-4)',
diff --git a/packages/web/src/utils/theme/default.ts b/packages/web/src/utils/theme/default.ts
index 7b7e387955..870f69703a 100644
--- a/packages/web/src/utils/theme/default.ts
+++ b/packages/web/src/utils/theme/default.ts
@@ -46,6 +46,7 @@ const theme = {
'--accent-orange-light-1': '#FFA70F',
'--accent-purple': '#8E51CF',
+ '--special-light-green': '#13c65a',
/* Semantic text */
'--text-default': 'var(--neutral)',