Skip to content

Commit 72fcc88

Browse files
authored
[PAY-1631] Implements post-purchase content on web (#3898)
1 parent 0f28578 commit 72fcc88

File tree

10 files changed

+225
-14
lines changed

10 files changed

+225
-14
lines changed

packages/stems/src/assets/styles/colors.css

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ root {
6262
--accent-blue: #1ba1f1;
6363
--accent-purple: #8e51cf;
6464

65+
--special-green: #0f9e48;
6566
--special-light-green: #13c65a;
6667

6768
/* Semantic text */

packages/web/src/components/notification/Notification/components/TwitterShareButton.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type TwitterShareButtonProps = {
4545
hideText?: boolean
4646
} & (StaticTwitterProps | DynamicTwitterProps)
4747

48+
// TODO: Migrate this to deriving from components/TwitterShareButton, similar to mobile
49+
// https://linear.app/audius/issue/PAY-1722/consolidate-twittersharebuttons-on-web
4850
export const TwitterShareButton = (props: TwitterShareButtonProps) => {
4951
const { url = null, className, hideText, ...other } = props
5052
const record = useRecord()

packages/web/src/components/premium-content-purchase-modal/PremiumContentPurchaseModal.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ export const PremiumContentPurchaseModal = () => {
8080
{track ? (
8181
<ModalContentPages currentPage={currentStep}>
8282
<LoadingPage />
83-
<PurchaseDetailsPage track={track} currentBalance={balance} />
83+
<PurchaseDetailsPage
84+
track={track}
85+
currentBalance={balance}
86+
onViewTrackClicked={handleClose}
87+
/>
8488
</ModalContentPages>
8589
) : null}
8690
</Modal>

packages/web/src/components/premium-content-purchase-modal/components/PurchaseDetailsPage.module.css

+18
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@
1919
stroke: currentColor;
2020
}
2121

22+
.purchaseSuccessfulContainer {
23+
align-items: center;
24+
display: flex;
25+
gap: var(--unit-2);
26+
justify-content: center;
27+
padding: var(--unit-3);
28+
}
29+
30+
.completionCheck {
31+
align-items: center;
32+
background: var(--special-green);
33+
border-radius: 50%;
34+
display: flex;
35+
height: var(--unit-4);
36+
justify-content: center;
37+
width: var(--unit-4);
38+
}
39+
2240
.errorContainer {
2341
display: flex;
2442
gap: var(--unit-2);

packages/web/src/components/premium-content-purchase-modal/components/PurchaseDetailsPage.tsx

+58-11
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,25 @@ import {
88
isPremiumContentUSDCPurchaseGated,
99
purchaseContentActions,
1010
purchaseContentSelectors,
11+
PurchaseContentStage,
1112
Track,
1213
UserTrackMetadata
1314
} from '@audius/common'
14-
import { HarmonyButton, IconError } from '@audius/stems'
15+
import {
16+
HarmonyButton,
17+
HarmonyPlainButton,
18+
HarmonyPlainButtonSize,
19+
HarmonyPlainButtonType,
20+
IconCaretRight,
21+
IconCheck,
22+
IconError
23+
} from '@audius/stems'
1524
import { useDispatch, useSelector } from 'react-redux'
1625

1726
import { Icon } from 'components/Icon'
1827
import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'
1928
import { LockedTrackDetailsTile } from 'components/track/LockedTrackDetailsTile'
29+
import { TwitterShareButton } from 'components/twitter-share-button/TwitterShareButton'
2030
import { Text } from 'components/typography'
2131

2232
import { FormatPrice } from './FormatPrice'
@@ -31,7 +41,11 @@ const { getPurchaseContentFlowStage, getPurchaseContentError } =
3141
const messages = {
3242
buy: 'Buy',
3343
purchasing: 'Purchasing',
34-
error: 'Your purchase was unsuccessful.'
44+
purchaseSuccessful: 'Your Purchase Was Successful!',
45+
error: 'Your purchase was unsuccessful.',
46+
// TODO: PAY-1723
47+
shareButtonContent: 'I just purchased a track on Audius!',
48+
viewTrack: 'View Track'
3549
}
3650

3751
const ContentPurchaseError = () => {
@@ -46,16 +60,19 @@ const ContentPurchaseError = () => {
4660
export type PurchaseDetailsPageProps = {
4761
currentBalance?: BNUSDC
4862
track: UserTrackMetadata
63+
onViewTrackClicked: () => void
4964
}
5065

5166
export const PurchaseDetailsPage = ({
5267
currentBalance,
53-
track
68+
track,
69+
onViewTrackClicked
5470
}: PurchaseDetailsPageProps) => {
5571
const dispatch = useDispatch()
5672
const stage = useSelector(getPurchaseContentFlowStage)
5773
const error = useSelector(getPurchaseContentError)
5874
const isUnlocking = !error && isContentPurchaseInProgress(stage)
75+
const isPurchased = stage === PurchaseContentStage.FINISH
5976

6077
const onClickBuy = useCallback(() => {
6178
if (isUnlocking) return
@@ -102,15 +119,45 @@ export const PurchaseDetailsPage = ({
102119
track={track as unknown as Track}
103120
owner={track.user}
104121
/>
105-
<PurchaseSummaryTable {...purchaseSummaryValues} />
106-
<PayToUnlockInfo />
107-
<HarmonyButton
108-
disabled={isUnlocking}
109-
color='specialLightGreen'
110-
onClick={onClickBuy}
111-
text={textContent}
112-
fullWidth
122+
<PurchaseSummaryTable
123+
{...purchaseSummaryValues}
124+
isPurchased={isPurchased}
113125
/>
126+
{isPurchased ? (
127+
<>
128+
<div className={styles.purchaseSuccessfulContainer}>
129+
<div className={styles.completionCheck}>
130+
<Icon icon={IconCheck} size='xxSmall' color='white' />
131+
</div>
132+
<Text variant='heading' size='small'>
133+
{messages.purchaseSuccessful}
134+
</Text>
135+
</div>
136+
<TwitterShareButton
137+
fullWidth
138+
type='static'
139+
shareText={messages.shareButtonContent}
140+
/>
141+
<HarmonyPlainButton
142+
onClick={onViewTrackClicked}
143+
iconRight={IconCaretRight}
144+
variant={HarmonyPlainButtonType.SUBDUED}
145+
size={HarmonyPlainButtonSize.LARGE}
146+
text={messages.viewTrack}
147+
/>
148+
</>
149+
) : (
150+
<>
151+
<PayToUnlockInfo />
152+
<HarmonyButton
153+
disabled={isUnlocking}
154+
color='specialLightGreen'
155+
onClick={onClickBuy}
156+
text={textContent}
157+
fullWidth
158+
/>
159+
</>
160+
)}
114161
{error ? <ContentPurchaseError /> : null}
115162
</div>
116163
)

packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const messages = {
1212
alwaysZero: 'Always $0',
1313
existingBalance: 'Existing Balance',
1414
youPay: 'You Pay',
15+
youPaid: 'You Paid',
1516
zero: '$0',
1617
price: (val: string) => `$${val}`
1718
}
@@ -21,13 +22,15 @@ type PurchaseSummaryTableProps = {
2122
artistCut: number
2223
basePrice: number
2324
existingBalance?: number
25+
isPurchased?: boolean
2426
}
2527

2628
export const PurchaseSummaryTable = ({
2729
amountDue,
2830
artistCut,
2931
basePrice,
30-
existingBalance
32+
existingBalance,
33+
isPurchased
3134
}: PurchaseSummaryTableProps) => {
3235
return (
3336
<Text className={styles.container}>
@@ -49,7 +52,7 @@ export const PurchaseSummaryTable = ({
4952
</div>
5053
) : null}
5154
<Text className={styles.row} variant='title'>
52-
<span>{messages.youPay}</span>
55+
<span>{isPurchased ? messages.youPaid : messages.youPay}</span>
5356
<FormatPrice
5457
className={styles.finalPrice}
5558
basePrice={basePrice}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { MouseEventHandler, useCallback } from 'react'
2+
3+
import {
4+
Nullable,
5+
useTwitterButtonStatus,
6+
CommonState,
7+
cacheUsersActions,
8+
cacheUsersSelectors
9+
} from '@audius/common'
10+
import { HarmonyButton, HarmonyButtonProps } from '@audius/stems'
11+
import { useDispatch, useSelector } from 'react-redux'
12+
13+
import { ReactComponent as IconTwitterBird } from 'assets/img/iconTwitterBird.svg'
14+
import { useRecord, TrackEvent } from 'common/store/analytics/actions'
15+
import { openTwitterLink } from 'utils/tweet'
16+
17+
const { fetchUserSocials } = cacheUsersActions
18+
const { getUser } = cacheUsersSelectors
19+
20+
const messages = {
21+
share: 'Share to Twitter'
22+
}
23+
24+
type StaticTwitterProps = {
25+
type: 'static'
26+
shareText: string
27+
analytics?: TrackEvent
28+
}
29+
30+
type DynamicTwitterProps = {
31+
type: 'dynamic'
32+
handle: string
33+
name?: string
34+
additionalHandle?: string
35+
shareData: (
36+
twitterHandle: string,
37+
otherTwitterHandle?: Nullable<string>
38+
) => Nullable<{ shareText: string; analytics: TrackEvent }>
39+
}
40+
41+
type TwitterShareButtonProps = {
42+
url?: string
43+
hideText?: boolean
44+
fullWidth?: HarmonyButtonProps['fullWidth']
45+
size?: HarmonyButtonProps['size']
46+
} & (StaticTwitterProps | DynamicTwitterProps)
47+
48+
export const TwitterShareButton = (props: TwitterShareButtonProps) => {
49+
const { url = null, fullWidth, size, hideText, ...other } = props
50+
const record = useRecord()
51+
const dispatch = useDispatch()
52+
53+
const user = useSelector((state: CommonState) =>
54+
getUser(state, { handle: 'handle' in other ? other.handle : undefined })
55+
)
56+
57+
const additionalUser = useSelector((state: CommonState) =>
58+
getUser(state, {
59+
handle: 'additionalHandle' in other ? other.additionalHandle : undefined
60+
})
61+
)
62+
63+
const {
64+
userName,
65+
additionalUserName,
66+
shareTwitterStatus,
67+
twitterHandle,
68+
additionalTwitterHandle,
69+
setLoading,
70+
setIdle
71+
} = useTwitterButtonStatus(user, additionalUser)
72+
73+
const handleClick: MouseEventHandler<HTMLButtonElement> = useCallback(
74+
(e) => {
75+
e.stopPropagation()
76+
if (other.type === 'static') {
77+
openTwitterLink(url, other.shareText)
78+
if (other.analytics) {
79+
record(other.analytics)
80+
}
81+
}
82+
if (other.type === 'dynamic') {
83+
dispatch(fetchUserSocials(other.handle))
84+
if (other.additionalHandle) {
85+
dispatch(fetchUserSocials(other.additionalHandle))
86+
}
87+
setLoading()
88+
}
89+
},
90+
[url, other, dispatch, record, setLoading]
91+
)
92+
93+
if (
94+
other.type === 'dynamic' &&
95+
shareTwitterStatus === 'success' &&
96+
userName &&
97+
(other.additionalHandle ? additionalUserName : true)
98+
) {
99+
const handle = twitterHandle ? `@${twitterHandle}` : userName
100+
101+
const otherHandle = other.additionalHandle
102+
? additionalTwitterHandle
103+
? `@${additionalTwitterHandle}`
104+
: additionalUserName
105+
: null
106+
107+
const twitterData = other.shareData(handle, otherHandle)
108+
if (twitterData) {
109+
const { shareText, analytics } = twitterData
110+
openTwitterLink(url, shareText)
111+
if (analytics) {
112+
record(analytics)
113+
}
114+
setIdle()
115+
}
116+
}
117+
118+
const colorOverride: Partial<HarmonyButtonProps> = {
119+
color: 'staticTwitterBlue'
120+
}
121+
122+
return (
123+
<HarmonyButton
124+
fullWidth={fullWidth}
125+
text={hideText ? null : messages.share}
126+
iconLeft={IconTwitterBird}
127+
size={size}
128+
{...colorOverride}
129+
onClick={handleClick}
130+
/>
131+
)
132+
}

packages/web/src/utils/theme/dark.ts

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const theme = {
3636
'--accent-red': '#F9344C',
3737
'--accent-red-dark-1': '#C43047',
3838

39+
'--special-green': '#6cdf44',
3940
'--special-light-green': '#13c65a',
4041

4142
'--page-header-gradient-color-1': '#7652CC',

packages/web/src/utils/theme/default.ts

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const theme = {
4646
'--accent-orange-light-1': '#FFA70F',
4747

4848
'--accent-purple': '#8E51CF',
49+
'--special-green': '#0f9e48',
4950
'--special-light-green': '#13c65a',
5051

5152
'--page-header-gradient-color-1': '#5B23E1',

packages/web/src/utils/theme/matrix.ts

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const theme = {
3535

3636
'--accent-red': '#F9344C',
3737

38+
'--special-green': '#6cdf44',
39+
3840
'--page-header-gradient-color-1': '#4FF069',
3941
'--page-header-gradient-color-2': '#09BD51',
4042
'--page-header-gradient':

0 commit comments

Comments
 (0)