Skip to content

Commit

Permalink
[PAY-2400] Break CoinflowWithdrawModal out into its own modal (#7357)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickyrombo authored Jan 27, 2024
1 parent f4fe982 commit a15e3d8
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createModal } from '../createModal'

export type CoinflowWithdrawModalState = {
amount: number
}

const coinflowWithdrawModal = createModal<CoinflowWithdrawModalState>({
reducerPath: 'CoinflowWithdraw',
initialState: {
isOpen: false,
amount: 5
},
sliceSelector: (state) => state.ui.modals
})

export const {
hook: useCoinflowWithdrawModal,
actions: coinflowWithdrawModalActions,
reducer: coinflowWithdrawModalReducer
} = coinflowWithdrawModal
1 change: 1 addition & 0 deletions packages/common/src/store/ui/modals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { sagas as modalsSagas } from './sagas'

export * from './create-chat-modal'
export * from './coinflow-onramp-modal'
export * from './coinflow-withdraw-modal'
export * from './leaving-audius-modal'
export * from './inbox-unavailable-modal'
export * from './usdc-purchase-details-modal'
Expand Down
3 changes: 2 additions & 1 deletion packages/common/src/store/ui/modals/parentSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export const initialState: BasicModalsState = {
USDCManualTransferModal: { isOpen: false },
CoinflowOnramp: { isOpen: false },
AddFundsModal: { isOpen: false },
Welcome: { isOpen: false }
Welcome: { isOpen: false },
CoinflowWithdraw: { isOpen: false }
}

const slice = createSlice({
Expand Down
4 changes: 3 additions & 1 deletion packages/common/src/store/ui/modals/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { usdcManualTransferModalReducer } from './usdc-manual-transfer-modal'
import { usdcPurchaseDetailsModalReducer } from './usdc-purchase-details-modal'
import { usdcTransactionDetailsModalReducer } from './usdc-transaction-details-modal'
import { withdrawUSDCModalReducer } from './withdraw-usdc-modal'
import { coinflowWithdrawModalReducer } from './coinflow-withdraw-modal'

/**
* Create a bunch of reducers that do nothing, so that the state is maintained and not lost through the child reducers
Expand Down Expand Up @@ -42,7 +43,8 @@ const combinedReducers = combineReducers({
AddFundsModal: addFundsModalReducer,
USDCTransactionDetailsModal: usdcTransactionDetailsModalReducer,
PremiumContentPurchaseModal: premiumContentPurchaseModalReducer,
CoinflowOnramp: coinflowOnrampModalReducer
CoinflowOnramp: coinflowOnrampModalReducer,
CoinflowWithdraw: coinflowWithdrawModalReducer
})

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/store/ui/modals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { USDCManualTransferModalState } from './usdc-manual-transfer-modal'
import { USDCPurchaseDetailsModalState } from './usdc-purchase-details-modal'
import { USDCTransactionDetailsModalState } from './usdc-transaction-details-modal'
import { WithdrawUSDCModalState } from './withdraw-usdc-modal'
import { CoinflowWithdrawModalState } from './coinflow-withdraw-modal'

export type Modals =
| 'TiersExplainer'
Expand Down Expand Up @@ -67,6 +68,7 @@ export type Modals =
| 'USDCManualTransferModal'
| 'AddFundsModal'
| 'Welcome'
| 'CoinflowWithdraw'

export type BasicModalsState = {
[modal in Modals]: BaseModalState
Expand All @@ -85,6 +87,7 @@ export type StatefulModalsState = {
USDCManualTransferModal: USDCManualTransferModalState
AddFundsModal: AddFundsModalState
PremiumContentPurchaseModal: PremiumContentPurchaseModalState
CoinflowWithdraw: CoinflowWithdrawModalState
}

export type ModalsState = BasicModalsState & StatefulModalsState
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.modalWrapper {
height: 100%;
}

.modalBody {
height: 100%;
max-width: 720px;
max-height: 800px;
}

.modalWrapper iframe {
max-height: 100% !important;
min-height: 800px !important;
max-width: 100% !important;
margin: 0 auto !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useCallback } from 'react'

import {
useCoinflowAdapter,
useCoinflowWithdrawModal,
withdrawUSDCActions
} from '@audius/common'
import { CoinflowWithdraw } from '@coinflowlabs/react'
import { useDispatch } from 'react-redux'

import ModalDrawer from 'pages/audio-rewards-page/components/modals/ModalDrawer'
import { env } from 'services/env'
import zIndex from 'utils/zIndex'

import styles from './CoinflowWithdrawModal.module.css'

const { coinflowWithdrawalCanceled, coinflowWithdrawalSucceeded } =
withdrawUSDCActions

const parseTransactionFromSuccessParams = (params: string) => {
try {
const parsed = JSON.parse(params)
return parsed.data as string
} catch (e) {
console.error(
`Failed to parse transaction from params: ${params}, received error: ${e}`
)
return ''
}
}

const MERCHANT_ID = env.COINFLOW_MERCHANT_ID
const IS_PRODUCTION = env.ENVIRONMENT === 'production'

export const CoinflowWithdrawModal = () => {
const {
data: { amount },
isOpen,
onClose,
onClosed
} = useCoinflowWithdrawModal()

const adapter = useCoinflowAdapter()
const dispatch = useDispatch()

const handleClose = useCallback(() => {
dispatch(coinflowWithdrawalCanceled())
onClose()
}, [dispatch, onClose])

const handleSuccess = useCallback(
(params: string) => {
const transaction = parseTransactionFromSuccessParams(params)
dispatch(coinflowWithdrawalSucceeded({ transaction }))
onClose()
},
[dispatch]
)

const showContent = isOpen && adapter

return (
<ModalDrawer
bodyClassName={styles.modalBody}
wrapperClassName={styles.modalWrapper}
zIndex={zIndex.COINFLOW_ONRAMP_MODAL}
isFullscreen
isOpen={isOpen}
onClose={handleClose}
onClosed={onClosed}
>
{showContent ? (
<CoinflowWithdraw
amount={amount}
lockAmount={true}
wallet={adapter.wallet}
connection={adapter.connection}
onSuccess={handleSuccess}
merchantId={MERCHANT_ID || ''}
env={IS_PRODUCTION ? 'prod' : 'sandbox'}
blockchain='solana'
/>
) : null}
</ModalDrawer>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.spinner {
height: var(--unit-10);
width: var(--unit-10);
align-self: center;
}
Original file line number Diff line number Diff line change
@@ -1,67 +1,13 @@
import { useCallback, useState } from 'react'
import { Flex } from '@audius/harmony'

import { useCoinflowAdapter, withdrawUSDCActions } from '@audius/common'
import { CoinflowWithdraw } from '@coinflowlabs/react'
import { useField } from 'formik'
import { useDispatch } from 'react-redux'
import { useUnmount } from 'react-use'
import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'

import { env } from 'services/env'

import { AMOUNT, WithdrawFormValues } from '../types'

const { coinflowWithdrawalCanceled, coinflowWithdrawalSucceeded } =
withdrawUSDCActions

const parseTransactionFromSuccessParams = (params: string) => {
try {
const parsed = JSON.parse(params)
return parsed.data as string
} catch (e) {
console.error(
`Failed to parse transaction from params: ${params}, received error: ${e}`
)
return ''
}
}

const MERCHANT_ID = env.COINFLOW_MERCHANT_ID
const IS_PRODUCTION = env.ENVIRONMENT === 'production'
import styles from './CoinflowWithdrawPage.module.css'

export const CoinflowWithdrawPage = () => {
const adapter = useCoinflowAdapter()
const dispatch = useDispatch()
const [{ value: amountCents }] =
useField<WithdrawFormValues[typeof AMOUNT]>(AMOUNT)
const [completed, setCompleted] = useState(false)

useUnmount(() => {
// There is no cancelation callback for coinflow, but we want
// to make sure any waiting saga finishes.
if (!completed) {
dispatch(coinflowWithdrawalCanceled())
}
})

const handleSuccess = useCallback(
(params: string) => {
const transaction = parseTransactionFromSuccessParams(params)
setCompleted(true)
dispatch(coinflowWithdrawalSucceeded({ transaction }))
},
[dispatch]
return (
<Flex direction={'column'}>
<LoadingSpinner className={styles.spinner} />
</Flex>
)

return adapter ? (
<CoinflowWithdraw
amount={amountCents / 100}
lockAmount={true}
wallet={adapter.wallet}
connection={adapter.connection}
onSuccess={handleSuccess}
merchantId={MERCHANT_ID || ''}
env={IS_PRODUCTION ? 'prod' : 'sandbox'}
blockchain='solana'
/>
) : null
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import {
CoinflowWithdrawState,
withdrawUSDCSelectors,
WithdrawUSDCModalPages,
useWithdrawUSDCModal
useWithdrawUSDCModal,
useCoinflowWithdrawModal
} from '@audius/common'
import { Flex, Text } from '@audius/harmony'
import { useField } from 'formik'
import { useSelector } from 'react-redux'

import LoadingSpinner from 'components/loading-spinner/LoadingSpinner'

import { WithdrawFormValues, AMOUNT } from '../types'

import styles from './PrepareTransfer.module.css'

const { getCoinflowState } = withdrawUSDCSelectors
Expand All @@ -23,13 +27,17 @@ const messages = {

export const PrepareTransfer = () => {
const coinflowState = useSelector(getCoinflowState)
const { onOpen } = useCoinflowWithdrawModal()
const { setData } = useWithdrawUSDCModal()
const [{ value: amountCents }] =
useField<WithdrawFormValues[typeof AMOUNT]>(AMOUNT)

useEffect(() => {
if (coinflowState === CoinflowWithdrawState.READY_FOR_WITHDRAWAL) {
setData({ page: WithdrawUSDCModalPages.COINFLOW_TRANSFER })
onOpen({ amount: amountCents / 100.0 })
}
}, [coinflowState, setData])
}, [coinflowState, setData, onOpen, amountCents])
return (
<Flex
direction='column'
Expand Down
4 changes: 3 additions & 1 deletion packages/web/src/pages/modals/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import TierExplainerModal from 'components/user-badges/TierExplainerModal'
import ConnectedUserListModal from 'components/user-list-modal/ConnectedUserListModal'
import { WelcomeModal } from 'components/welcome-modal/WelcomeModal'
import { WithdrawUSDCModal } from 'components/withdraw-usdc-modal/WithdrawUSDCModal'
import { CoinflowWithdrawModal } from 'components/withdraw-usdc-modal/components/CoinflowWithdrawModal'
import { useIsMobile } from 'hooks/useIsMobile'
import AudioBreakdownModal from 'pages/audio-rewards-page/components/modals/AudioBreakdownModal'
import { ChallengeRewardsModal } from 'pages/audio-rewards-page/components/modals/ChallengeRewardsModal'
Expand Down Expand Up @@ -108,7 +109,8 @@ const commonModalsMap: { [Modal in ModalTypes]?: ComponentType } = {
StripeOnRamp: StripeOnRampModal,
USDCPurchaseDetailsModal,
USDCTransactionDetailsModal,
AddFundsModal
AddFundsModal,
CoinflowWithdraw: CoinflowWithdrawModal
}

const commonModals = Object.entries(commonModalsMap) as [
Expand Down
33 changes: 30 additions & 3 deletions packages/web/src/store/application/ui/withdraw-usdc/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
withdrawUSDCModalActions,
WithdrawUSDCModalPages,
WithdrawMethod,
buyUSDCActions
buyUSDCActions,
Status
} from '@audius/common'
import {
createAssociatedTokenAccountInstruction,
Expand Down Expand Up @@ -52,9 +53,11 @@ const {
coinflowWithdrawalCanceled,
coinflowWithdrawalSucceeded,
withdrawUSDCFailed,
withdrawUSDCSucceeded
withdrawUSDCSucceeded,
cleanup: cleanupWithdrawUSDC
} = withdrawUSDCActions
const { set: setWithdrawUSDCModalData } = withdrawUSDCModalActions
const { set: setWithdrawUSDCModalData, close: closeWithdrawUSDCModal } =
withdrawUSDCModalActions
const { getFeePayer } = solanaSelectors

/**
Expand Down Expand Up @@ -359,6 +362,30 @@ function* doWithdrawUSDCCoinflow({
)
} else {
yield* put(buyUSDCActions.startRecoveryIfNecessary())
// Wait for the recovery to succeed or error
const action = yield* take<
ReturnType<typeof buyUSDCActions.recoveryStatusChanged>
>((action: any) => {
return (
action.type === buyUSDCActions.recoveryStatusChanged.type &&
(action?.payload?.status === Status.SUCCESS ||
action?.payload.status === Status.ERROR)
)
})
yield* put(cleanupWithdrawUSDC())
yield* put(closeWithdrawUSDCModal())
// Buy USDC recovery already logs to sentry and makes an analytics event
// so add some logs to help discern which flow the recovery was triggered
// from and help aid in debugging should this ever hit.
if (action.payload.status === Status.ERROR) {
// Breadcrumb hint:
console.warn(
'Failed to transfer funds back from root wallet:',
rootSolanaAccount.publicKey.toBase58()
)
// Console error for sentry issue
console.error('Failed to recover funds from Coinflow Withdraw')
}
}
} catch (e: unknown) {
const error = e as Error
Expand Down

0 comments on commit a15e3d8

Please sign in to comment.