Skip to content

Commit

Permalink
[PAY-3975] First weekly comment mobile UI (#11489)
Browse files Browse the repository at this point in the history
  • Loading branch information
dharit-tan authored Mar 1, 2025
1 parent 5a8dffb commit 69e690f
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 9 deletions.
8 changes: 6 additions & 2 deletions packages/common/src/utils/challenges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ export const challengeRewardsConfig: Record<
progressLabel: 'Ready to Claim'
},
[ChallengeName.FirstWeeklyComment]: {
shortTitle: 'first comment of the week',
title: 'First comment of the week',
shortTitle: 'First Comment of the Week',
title: 'First Comment of the Week',
description: () => 'Your first comment every week will earn $AUDIO.',
fullDescription: () => 'Your first comment every week will earn $AUDIO.',
panelButtonText: 'Comment on a Track',
Expand Down Expand Up @@ -416,6 +416,10 @@ export const getChallengeStatusLabel = (
return DEFAULT_STATUS_LABELS.READY_TO_CLAIM
}
return 'No Recent Activity'
case ChallengeName.FirstWeeklyComment:
if (challenge.disbursed_amount && !challenge.claimableAmount) {
return 'Resets Friday'
}
}

// Handle claimable state for non-aggregate rewards
Expand Down
5 changes: 5 additions & 0 deletions packages/harmony/src/assets/icons/ArrowRotate.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/harmony/src/icons/utilityIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import IconAllTimeSVG from '../assets/icons/AllTime.svg'
import IconAppearanceSVG from '../assets/icons/Appearance.svg'
import IconArrowLeftSVG from '../assets/icons/ArrowLeft.svg'
import IconArrowRightSVG from '../assets/icons/ArrowRight.svg'
import IconArrowRotateSVG from '../assets/icons/ArrowRotate.svg'
import IconArrowUpToLineSVG from '../assets/icons/ArrowUpToLine.svg'
import IconBlogSVG from '../assets/icons/Blog.svg'
import IconBoxHeartSVG from '../assets/icons/BoxHeart.svg'
Expand Down Expand Up @@ -251,6 +252,7 @@ export const IconPencil = IconPencilSVG as IconComponent
export const IconPin = IconPinSVG as IconComponent
export const IconUser = IconUserSVG as IconComponent
export const IconUserArrowRotate = IconUserArrowRotateSVG as IconComponent
export const IconArrowRotate = IconArrowRotateSVG as IconComponent
export const IconCollectible = IconCollectibleSVG as IconComponent
export const IconPlay = IconPlaySVG as IconComponent
export const IconUserFollow = IconUserFollowSVG as IconComponent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React from 'react'

import { ChallengeName } from '@audius/common/models'
import { ClaimStatus } from '@audius/common/store'
import {
formatNumberCommas,
getChallengeStatusLabel
} from '@audius/common/utils'
import { Platform } from 'react-native'

import {
Flex,
IconArrowRight,
Button,
Text,
IconArrowRotate,
IconCheck
} from '@audius/harmony-native'
import { getChallengeConfig } from 'app/utils/challenges'

import { ChallengeRewardsLayout } from './ChallengeRewardsLayout'
import { ClaimError } from './ClaimError'
import type { ChallengeContentProps } from './types'

const messages = {
rewardMapping: '$AUDIO/Week',
totalClaimed: (amount: string) => `${amount} $AUDIO Claimed`,
claimAudio: (amount: string) => `Claim ${amount} $AUDIO`,
close: 'Close'
}

export const FirstWeeklyCommentChallengeContent = ({
aaoErrorCode,
challenge,
challengeName,
claimStatus,
onClaim,
onClose
}: ChallengeContentProps) => {
const config = getChallengeConfig(ChallengeName.FirstWeeklyComment)
const claimInProgress =
claimStatus === ClaimStatus.CLAIMING ||
claimStatus === ClaimStatus.WAITING_FOR_RETRY
const claimError = claimStatus === ClaimStatus.ERROR

const description = challenge ? config.description(challenge) : ''
const statusText = challenge
? getChallengeStatusLabel(challenge, challengeName)
: ''

const statusLabel = (
<Flex
w='100%'
ph='xl'
border='default'
borderRadius='s'
backgroundColor='surface1'
>
<Flex
row
w='100%'
alignItems='center'
justifyContent='center'
gap='s'
pv='l'
>
{challenge?.claimableAmount ? (
<IconCheck size='s' color='subdued' />
) : challenge?.disbursed_amount && !challenge?.claimableAmount ? (
<IconArrowRotate size='s' color='subdued' />
) : null}
<Text
variant='label'
size='l'
color='subdued'
// iOS has a bug where emojis are not vertically aligned with the text
style={{ lineHeight: Platform.OS === 'ios' ? 0 : undefined }}
>
{statusText}
</Text>
</Flex>
{challenge?.disbursed_amount ? (
<Flex
w='100%'
row
justifyContent='center'
alignItems='center'
borderTop='default'
pv='l'
>
<Text variant='label' size='l' color='subdued'>
{messages.totalClaimed(
formatNumberCommas(challenge.disbursed_amount)
)}
</Text>
</Flex>
) : null}
</Flex>
)

const actions =
challenge?.claimableAmount && onClaim ? (
<Button
disabled={claimInProgress}
variant='primary'
onPress={onClaim}
isLoading={claimInProgress}
iconRight={IconArrowRight}
fullWidth
>
{messages.claimAudio(formatNumberCommas(challenge.claimableAmount))}
</Button>
) : (
<Button variant='secondary' onPress={onClose} fullWidth>
{messages.close}
</Button>
)

// Following the pattern from FirstWeeklyCommentChallengeModalContent
// where totalAmount is set to challenge.amount
const modifiedAmount = challenge?.amount ?? 0

return (
<ChallengeRewardsLayout
description={description}
amount={modifiedAmount}
rewardSubtext={messages.rewardMapping}
statusLabel={statusLabel}
actions={actions}
errorContent={
claimError ? <ClaimError aaoErrorCode={aaoErrorCode} /> : null
}
isCooldownChallenge={false}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ChallengeName } from '@audius/common/models'

import { AudioMatchingChallengeContent } from './AudioMatchingChallengeContent'
import { DefaultChallengeContent } from './DefaultChallengeContent'
import { FirstWeeklyCommentChallengeContent } from './FirstWeeklyCommentChallengeContent'
import { ListenStreakEndlessChallengeContent } from './ListenStreakEndlessChallengeContent'
import { ProfileCompletionChallengeContent } from './ProfileCompletionChallengeContent'
import { ReferralChallengeContent } from './ReferralChallengeContent'
Expand All @@ -21,6 +22,7 @@ export const challengeContentRegistry: ChallengeContentMap = {
[ChallengeName.ListenStreakEndless]: ListenStreakEndlessChallengeContent,
[ChallengeName.ProfileCompletion]: ProfileCompletionChallengeContent,
[ChallengeName.Referrals]: ReferralChallengeContent,
[ChallengeName.FirstWeeklyComment]: FirstWeeklyCommentChallengeContent,
default: DefaultChallengeContent
}

Expand Down
1 change: 1 addition & 0 deletions packages/mobile/src/harmony-native/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export { default as IconCloudUpload } from '@audius/harmony/src/assets/icons/Clo
export { default as IconPencil } from '@audius/harmony/src/assets/icons/Pencil.svg'
export { default as IconUser } from '@audius/harmony/src/assets/icons/User.svg'
export { default as IconUserArrowRotate } from '@audius/harmony/src/assets/icons/UserArrowRotate.svg'
export { default as IconArrowRotate } from '@audius/harmony/src/assets/icons/ArrowRotate.svg'
export { default as IconCollectible } from '@audius/harmony/src/assets/icons/Collectible.svg'
export { default as IconPlay } from '@audius/harmony/src/assets/icons/Play.svg'
export { default as IconUserFollow } from '@audius/harmony/src/assets/icons/UserFollow.svg'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ const validRewardIds: Set<ChallengeRewardID> = new Set([
ChallengeName.ReferralsVerified,
ChallengeName.Referred,
ChallengeName.TrackUpload,
ChallengeName.OneShot
ChallengeName.OneShot,
ChallengeName.FirstWeeklyComment
])

type ClaimableSummaryTableItem = SummaryTableItem & {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,115 @@
import { DefaultChallengeContent } from './DefaultChallengeContent'
import {
audioRewardsPageSelectors,
challengesSelectors,
ClaimStatus
} from '@audius/common/store'
import {
formatNumberCommas,
challengeRewardsConfig,
getChallengeStatusLabel
} from '@audius/common/utils'
import { Flex, IconArrowRotate, IconCheck, Text } from '@audius/harmony'
import { useSelector } from 'react-redux'

import { useIsMobile } from 'hooks/useIsMobile'

import { ChallengeRewardsLayout } from './ChallengeRewardsLayout'
import { ClaimButton } from './ClaimButton'
import { type DefaultChallengeProps } from './types'

export const FirstWeeklyCommentChallengeModalContent = (
props: DefaultChallengeProps
) => {
const { challenge } = props
const { getOptimisticUserChallenges } = challengesSelectors
const { getUndisbursedUserChallenges, getClaimStatus } =
audioRewardsPageSelectors

const messages = {
rewardSubtext: '$AUDIO/Week',
totalClaimed: (amount: string) => `${amount} $AUDIO Claimed`
}

export const FirstWeeklyCommentChallengeModalContent = ({
challenge,
challengeName,
onNavigateAway,
errorContent
}: DefaultChallengeProps) => {
const isMobile = useIsMobile()
const userChallenge = useSelector(getOptimisticUserChallenges)[challengeName]
const undisbursedUserChallenges = useSelector(getUndisbursedUserChallenges)
const claimStatus = useSelector(getClaimStatus)
const claimInProgress =
claimStatus === ClaimStatus.CLAIMING ||
claimStatus === ClaimStatus.WAITING_FOR_RETRY

// Following the pattern from mobile implementation
const modifiedChallenge = challenge
? {
...challenge,
totalAmount: challenge?.amount ?? 0
}
: undefined

return <DefaultChallengeContent {...props} challenge={modifiedChallenge} />
const { fullDescription } = challengeRewardsConfig[challengeName]

const progressStatusLabel = userChallenge ? (
<Flex
ph='xl'
backgroundColor='surface1'
border='default'
borderRadius='s'
column={isMobile}
>
<Flex
pv='l'
gap='s'
w='100%'
justifyContent={isMobile ? 'center' : 'flex-start'}
alignItems='center'
>
{userChallenge?.claimableAmount ? (
<IconCheck size='s' color='subdued' />
) : userChallenge?.disbursed_amount &&
!userChallenge?.claimableAmount ? (
<IconArrowRotate size='s' color='subdued' />
) : null}
<Text variant='label' size='l' color='subdued'>
{getChallengeStatusLabel(userChallenge, challengeName)}
</Text>
</Flex>
{userChallenge.disbursed_amount ? (
<Flex
pv='l'
w='100%'
justifyContent={isMobile ? 'center' : 'flex-end'}
alignItems='center'
borderTop={isMobile ? 'default' : undefined}
>
<Text variant='label' size='l' color='subdued'>
{messages.totalClaimed(
formatNumberCommas(userChallenge.disbursed_amount.toString())
)}
</Text>
</Flex>
) : null}
</Flex>
) : null

return (
<ChallengeRewardsLayout
description={
<Text variant='body'>{fullDescription?.(modifiedChallenge)}</Text>
}
amount={modifiedChallenge?.totalAmount}
rewardSubtext={messages.rewardSubtext}
progressStatusLabel={progressStatusLabel}
actions={
<ClaimButton
challenge={modifiedChallenge}
claimInProgress={claimInProgress}
undisbursedChallenges={undisbursedUserChallenges}
onClose={onNavigateAway}
/>
}
errorContent={errorContent}
/>
)
}

0 comments on commit 69e690f

Please sign in to comment.