Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

Commit

Permalink
[PAY-1559][PAY-1560] Adds USDC variants for track tiles on web/mobile…
Browse files Browse the repository at this point in the history
… web (#3724)
  • Loading branch information
schottra authored Jul 11, 2023
1 parent 54371aa commit 53e815a
Show file tree
Hide file tree
Showing 23 changed files with 271 additions and 98 deletions.
54 changes: 45 additions & 9 deletions packages/common/src/utils/wallet.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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)}`
}
3 changes: 3 additions & 0 deletions packages/stems/src/assets/icons/iconCart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/stems/src/assets/styles/colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/stems/src/components/Icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions packages/stems/src/styles/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export type ColorValue =
| 'accentOrangeLight1'
| 'accentPurple'
| 'accentBlue'
| 'specialLightGreen'
2 changes: 2 additions & 0 deletions packages/web/src/assets/styles/colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@

--accent-purple: #8e51cf;

--special-light-green: #13c65a;

--ai-primary: #1fd187;
--ai-secondary: #0fc578;

Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/components/dog-ear/DogEar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
background: var(--accent-blue);
}

.purchase {
background: var(--special-light-green);
}

.star {
background: linear-gradient(314.61deg, #7e1bcc 0%, #a22feb 100%);
}
Expand Down
10 changes: 9 additions & 1 deletion packages/web/src/components/dog-ear/DogEar.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -29,6 +34,8 @@ export const DogEar = (props: DogEarProps) => {
return <IconLock />
case DogEarType.COLLECTIBLE_GATED:
return <IconCollectible />
case DogEarType.USDC_PURCHASE:
return <IconCart />
case DogEarType.SPECIAL_ACCESS:
return <IconSpecialAccess />
}
Expand All @@ -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
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 10 additions & 2 deletions packages/web/src/components/track/LockedStatusBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div
className={cn(styles.container, locked ? styles.locked : styles.unlocked)}
className={cn(
styles.container,
locked ? styles.locked : styles.unlocked,
variant === 'premium' ? styles.premium : styles.gated
)}
>
<LockComponent className={styles.icon} />
</div>
Expand Down
38 changes: 38 additions & 0 deletions packages/web/src/components/track/PremiumConditionsPill.module.css
Original file line number Diff line number Diff line change
@@ -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);
}
50 changes: 50 additions & 0 deletions packages/web/src/components/track/PremiumConditionsPill.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<LoadingSpinner className={styles.spinner} />
) : isPurchase ? null : (
<IconLock />
)

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 (
<div className={cn(styles.container, colorStyle)}>
{icon}
{message}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
15 changes: 11 additions & 4 deletions packages/web/src/components/track/PremiumContentLabel.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 (
<div className={cn(styles.labelContainer, { [colorStyle]: showColor })}>
Expand Down
21 changes: 18 additions & 3 deletions packages/web/src/components/track/PremiumTrackSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
removeNullable,
isPremiumContentFollowGated,
isPremiumContentCollectibleGated,
isPremiumContentTipGated
isPremiumContentTipGated,
isPremiumContentUSDCPurchaseGated
} from '@audius/common'
import {
Button,
Expand Down Expand Up @@ -303,7 +304,14 @@ const LockedPremiumTrackSection = ({
styles.premiumContentSectionTitle
)}
>
<LockedStatusBadge locked />
<LockedStatusBadge
locked
variant={
isPremiumContentUSDCPurchaseGated(premiumConditions)
? 'premium'
: 'gated'
}
/>
{messages.howToUnlock}
</div>
{renderLockedDescription()}
Expand Down Expand Up @@ -484,7 +492,14 @@ const UnlockedPremiumTrackSection = ({
<IconSpecialAccess className={styles.specialAccessIcon} />
)
) : (
<LockedStatusBadge locked={false} />
<LockedStatusBadge
locked={false}
variant={
isPremiumContentUSDCPurchaseGated(premiumConditions)
? 'premium'
: 'gated'
}
/>
)}
{isOwner
? isPremiumContentCollectibleGated(premiumConditions)
Expand Down
Loading

0 comments on commit 53e815a

Please sign in to comment.