Skip to content

Commit

Permalink
[PAY-1934] Fix some issues with purchase modal state (#6231)
Browse files Browse the repository at this point in the history
  • Loading branch information
schottra authored Oct 4, 2023
1 parent 93cac56 commit 0c510dc
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 49 deletions.
11 changes: 9 additions & 2 deletions packages/mobile/src/components/drawer/NativeDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { DrawerProps } from './Drawer'
import Drawer from './Drawer'

type NativeDrawerProps = SetOptional<DrawerProps, 'isOpen' | 'onClose'> & {
blockClose?: boolean
drawerName: DrawerName
}

Expand All @@ -17,14 +18,20 @@ type NativeDrawerProps = SetOptional<DrawerProps, 'isOpen' | 'onClose'> & {
* opening and closing.
*/
export const NativeDrawer = (props: NativeDrawerProps) => {
const { drawerName, onClose: onCloseProp, ...other } = props
const {
blockClose = true,
drawerName,
onClose: onCloseProp,
...other
} = props

const { isOpen, onClose, onClosed, visibleState } = useDrawer(drawerName)

const handleClose = useCallback(() => {
if (blockClose) return
onCloseProp?.()
onClose()
}, [onCloseProp, onClose])
}, [blockClose, onCloseProp, onClose])

if (visibleState === false) return null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import type { PurchasableTrackMetadata } from '@audius/common'
import {
PurchaseContentStage,
formatPrice,
isContentPurchaseInProgress,
isTrackPurchasable,
payExtraAmountPresetValues,
purchaseContentActions,
purchaseContentSelectors,
statusIsNotFinalized,
useGetTrackById,
usePurchaseContentFormConfiguration
} from '@audius/common'
import { Formik, useFormikContext } from 'formik'
import { Linking, View, ScrollView, TouchableOpacity } from 'react-native'
import { useDispatch } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { toFormikValidationSchema } from 'zod-formik-adapter'

import IconCart from 'app/assets/images/iconCart.svg'
Expand All @@ -35,6 +37,9 @@ import { PurchaseSuccess } from './PurchaseSuccess'
import { PurchaseSummaryTable } from './PurchaseSummaryTable'
import { usePurchaseContentFormState } from './hooks/usePurchaseContentFormState'

const { getPurchaseContentFlowStage, getPurchaseContentError } =
purchaseContentSelectors

const PREMIUM_TRACK_PURCHASE_MODAL_NAME = 'PremiumTrackPurchase'

const messages = {
Expand Down Expand Up @@ -188,7 +193,9 @@ const RenderForm = ({ track }: { track: PurchasableTrackMetadata }) => {
<>
<ScrollView contentContainerStyle={styles.formContentContainer}>
<TrackDetailsTile trackId={track.track_id} />
<PayExtraFormSection amountPresets={payExtraAmountPresetValues} />
{isPurchaseSuccessful ? null : (
<PayExtraFormSection amountPresets={payExtraAmountPresetValues} />
)}
<PurchaseSummaryTable
{...purchaseSummaryValues}
isPurchaseSuccessful={isPurchaseSuccessful}
Expand All @@ -213,33 +220,37 @@ const RenderForm = ({ track }: { track: PurchasableTrackMetadata }) => {
</View>
)}
</ScrollView>
<View style={styles.formActions}>
{error ? (
<View style={styles.errorContainer}>
<IconError
fill={accentRed}
width={spacing(5)}
height={spacing(5)}
/>
<Text weight='medium' colorValue={accentRed}>
{messages.error}
</Text>
</View>
) : null}
<Button
onPress={submitForm}
disabled={isUnlocking}
title={
isUnlocking ? messages.purchasing : messages.buy(formatPrice(price))
}
variant={'primary'}
size='large'
color={specialLightGreen}
iconPosition='left'
icon={isUnlocking ? LoadingSpinner : undefined}
fullWidth
/>
</View>
{isPurchaseSuccessful ? null : (
<View style={styles.formActions}>
{error ? (
<View style={styles.errorContainer}>
<IconError
fill={accentRed}
width={spacing(5)}
height={spacing(5)}
/>
<Text weight='medium' colorValue={accentRed}>
{messages.error}
</Text>
</View>
) : null}
<Button
onPress={submitForm}
disabled={isUnlocking}
title={
isUnlocking
? messages.purchasing
: messages.buy(formatPrice(price))
}
variant={'primary'}
size='large'
color={specialLightGreen}
iconPosition='left'
icon={isUnlocking ? LoadingSpinner : undefined}
fullWidth
/>
</View>
)}
</>
)
}
Expand All @@ -254,6 +265,9 @@ export const PremiumTrackPurchaseDrawer = () => {
{ id: trackId },
{ disabled: !trackId }
)
const stage = useSelector(getPurchaseContentFlowStage)
const error = useSelector(getPurchaseContentError)
const isUnlocking = !error && isContentPurchaseInProgress(stage)

const isLoading = statusIsNotFinalized(trackStatus)

Expand All @@ -268,6 +282,7 @@ export const PremiumTrackPurchaseDrawer = () => {

return (
<NativeDrawer
blockClose={isUnlocking}
drawerHeader={PremiumTrackPurchaseDrawerHeader}
drawerName={PREMIUM_TRACK_PURCHASE_MODAL_NAME}
onClosed={handleClosed}
Expand Down
15 changes: 13 additions & 2 deletions packages/web/src/components/drawer/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type DrawerProps = {
children: ReactNode
shouldClose?: boolean
onClose?: () => void
onClosed?: () => void
isFullscreen?: boolean
}

Expand Down Expand Up @@ -319,7 +320,12 @@ const interpolateBorderRadius = (r: number) => {
return `${r2}px ${r2}px 0px 0px`
}

const FullscreenDrawer = ({ children, isOpen, onClose }: DrawerProps) => {
const FullscreenDrawer = ({
children,
isOpen,
onClose,
onClosed
}: DrawerProps) => {
const drawerRef = useRef<HTMLDivElement | null>(null)
// Lock to prevent double scrollbars
useEffect(() => {
Expand Down Expand Up @@ -348,7 +354,12 @@ const FullscreenDrawer = ({ children, isOpen, onClose }: DrawerProps) => {
y: 1,
borderRadius: 40
},
config: slowWobble
config: slowWobble,
onDestroyed: () => {
if (!isOpen && onClosed) {
onClosed()
}
}
})
return (
<Portal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
usePremiumContentPurchaseModal,
usePurchaseContentFormConfiguration,
buyUSDCActions,
purchaseContentActions
purchaseContentActions,
purchaseContentSelectors,
isContentPurchaseInProgress
} from '@audius/common'
import { IconCart, ModalContent, ModalFooter, ModalHeader } from '@audius/stems'
import cn from 'classnames'
import { Formik } from 'formik'
import { useDispatch } from 'react-redux'
import { Formik, useFormikContext } from 'formik'
import { useDispatch, useSelector } from 'react-redux'
import { toFormikValidationSchema } from 'zod-formik-adapter'

import { Icon } from 'components/Icon'
Expand All @@ -33,6 +35,8 @@ import { usePurchaseContentFormState } from './hooks/usePurchaseContentFormState
const { startRecoveryIfNecessary, cleanup: cleanupUSDCRecovery } =
buyUSDCActions
const { cleanup } = purchaseContentActions
const { getPurchaseContentFlowStage, getPurchaseContentError } =
purchaseContentSelectors

const messages = {
completePurchase: 'Complete Purchase'
Expand All @@ -58,16 +62,10 @@ const RenderForm = ({
const { error, isUnlocking, purchaseSummaryValues, stage } =
usePurchaseContentFormState({ price })

// Attempt recovery once on re-mount of the form
useEffect(() => {
dispatch(startRecoveryIfNecessary)
}, [dispatch])
const { resetForm } = useFormikContext()

const handleClose = useCallback(() => {
dispatch(cleanupUSDCRecovery())
onClose()
dispatch(cleanup())
}, [dispatch, onClose])
// Reset form on track change
useEffect(() => resetForm, [track.track_id, resetForm])

// Navigate to track on successful purchase behind the modal
useEffect(() => {
Expand All @@ -82,7 +80,7 @@ const RenderForm = ({
<ModalForm>
<ModalHeader
className={cn(styles.modalHeader, { [styles.mobile]: mobile })}
onClose={handleClose}
onClose={onClose}
showDismissButton={!mobile}
>
<Text
Expand Down Expand Up @@ -123,12 +121,16 @@ const RenderForm = ({
}

export const PremiumContentPurchaseModal = () => {
const dispatch = useDispatch()
const {
isOpen,
onClose,
onClosed,
data: { contentId: trackId }
} = usePremiumContentPurchaseModal()
const stage = useSelector(getPurchaseContentFlowStage)
const error = useSelector(getPurchaseContentError)
const isUnlocking = !error && isContentPurchaseInProgress(stage)

const { data: track } = useGetTrackById(
{ id: trackId! },
Expand All @@ -140,15 +142,33 @@ export const PremiumContentPurchaseModal = () => {

const isValidTrack = track && isTrackPurchasable(track)

// Attempt recovery once on re-mount of the form
useEffect(() => {
dispatch(startRecoveryIfNecessary)
}, [dispatch])

const handleClose = useCallback(() => {
// Don't allow closing if we're in the middle of a purchase
if (!isUnlocking) {
onClose()
}
}, [isUnlocking, onClose])

const handleClosed = useCallback(() => {
onClosed()
dispatch(cleanup())
dispatch(cleanupUSDCRecovery())
}, [onClosed, dispatch])

if (track && !isValidTrack) {
console.error('PremiumContentPurchaseModal: Track is not purchasable')
}

return (
<ModalDrawer
isOpen={isOpen}
onClose={onClose}
onClosed={onClosed}
onClose={handleClose}
onClosed={handleClosed}
bodyClassName={styles.modal}
isFullscreen
useGradientTitle={false}
Expand All @@ -160,7 +180,7 @@ export const PremiumContentPurchaseModal = () => {
validationSchema={toFormikValidationSchema(validationSchema)}
onSubmit={onSubmit}
>
<RenderForm track={track} onClose={onClose} />
<RenderForm track={track} onClose={handleClose} />
</Formik>
) : null}
</ModalDrawer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ModalDrawer = (props: ModalDrawerProps) => {
<Drawer
isOpen={props.isOpen}
onClose={props.onClose}
onClosed={props.onClosed}
isFullscreen={
props.isFullscreen === undefined ? true : props.isFullscreen
}
Expand All @@ -47,6 +48,7 @@ const ModalDrawer = (props: ModalDrawerProps) => {
<Modal
isOpen={props.isOpen}
onClose={props.onClose}
onClosed={props.onClosed}
showTitleHeader={props.showTitleHeader}
showDismissButton={props.showDismissButton}
dismissOnClickOutside={props.dismissOnClickOutside}
Expand Down

0 comments on commit 0c510dc

Please sign in to comment.