Skip to content

Commit

Permalink
Feat/transfers with paypal and multiple payouts (#1104)
Browse files Browse the repository at this point in the history
* fully supporting transfers with paypal payments

* fixing payout update for paypal and supporting multiple payments
  • Loading branch information
alexanmtz authored Jun 9, 2024
1 parent d2742f4 commit af01487
Show file tree
Hide file tree
Showing 25 changed files with 1,170 additions and 155 deletions.
2 changes: 1 addition & 1 deletion frontend/src/actions/taskActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ const transferTask = (taskId) => {
if (transfer.data) {
if(transfer.data.error) {
return dispatch(
addNotification(task.data.error)
addNotification(transfer.data.error)
)
}
dispatch(addNotification('actions.task.transfer.success'))
Expand Down
49 changes: 48 additions & 1 deletion frontend/src/actions/transferActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const UPDATE_TRANSFER_REQUESTED = 'UPDATE_TRANSFER_REQUESTED'
const UPDATE_TRANSFER_SUCCESS = 'UPDATE_TRANSFER_SUCCESS'
const UPDATE_TRANSFER_FAILED = 'UPDATE_TRANSFER_FAILED'

const FETCH_TRANSFER_REQUESTED = 'FETCH_TRANSFER_REQUESTED'
const FETCH_TRANSFER_SUCCESS = 'FETCH_TRANSFER_SUCCESS'
const FETCH_TRANSFER_FAILED = 'FETCH_TRANSFER_FAILED'

const searchTransferRequested = () => {
return {
type: SEARCH_TRANSFER_REQUESTED,
Expand Down Expand Up @@ -91,13 +95,56 @@ const updateTransfer = (params) => (dispatch) => {
})
}

const fetchTransferRequested = () => {
return {
type: FETCH_TRANSFER_REQUESTED,
completed: false
}
}

const fetchTransferSuccess = (data) => {
return {
type: FETCH_TRANSFER_SUCCESS,
data: data,
completed: true
}
}

const fetchTransferFailed = (error) => {
return {
type: FETCH_TRANSFER,
error: error,
completed: true
}
}

const fetchTransfer = (id) => (dispatch) => {
dispatch(fetchTransferRequested())
return axios.get(api.API_URL + '/transfers/fetch/' + id).then(
transfer => {
if (transfer.data) {
return dispatch(fetchTransferSuccess(transfer.data))
}
if (transfer.error) {
return dispatch(fetchTransferFailed(transfer.error))
}
}
).catch(e => {
return dispatch(fetchTransferFailed(e))
})
}

export {
SEARCH_TRANSFER_REQUESTED,
SEARCH_TRANSFER_SUCCESS,
SEARCH_TRANSFER_FAILED,
UPDATE_TRANSFER_REQUESTED,
UPDATE_TRANSFER_SUCCESS,
UPDATE_TRANSFER_FAILED,
FETCH_TRANSFER_REQUESTED,
FETCH_TRANSFER_SUCCESS,
FETCH_TRANSFER_FAILED,
searchTransfer,
updateTransfer
updateTransfer,
fetchTransfer
}
12 changes: 9 additions & 3 deletions frontend/src/components/profile/payouts.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React, { useEffect } from 'react'
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'
import slugify from '@sindresorhus/slugify'
import moment from 'moment'
import {
Container,
Typography,
withStyles,
Chip
} from '@material-ui/core'
import { messages } from '../task/messages/task-messages'
import CustomPaginationActionsTable from './payout-table'

//Define messages for internationalization
Expand All @@ -21,6 +19,10 @@ const payoutMessages = defineMessages({
id: 'profile.payouts.headerStatus',
defaultMessage: 'Status'
},
headerMethod: {
id: 'profile.payouts.headerMethod',
defaultMessage: 'Method'
},
headerValue: {
id: 'profile.payouts.headerValue',
defaultMessage: 'Value'
Expand Down Expand Up @@ -99,6 +101,7 @@ const Payouts = ({ searchPayout, payouts, user, intl }) => {
<CustomPaginationActionsTable
tableHead={ [
intl.formatMessage(payoutMessages.headerStatus),
intl.formatMessage(payoutMessages.headerMethod),
intl.formatMessage(payoutMessages.headerValue),
intl.formatMessage(payoutMessages.headerCreated),
] }
Expand All @@ -107,7 +110,10 @@ const Payouts = ({ searchPayout, payouts, user, intl }) => {
...payouts,
data: payouts.data.map(t => [
<Chip label={ t.status } />,
`${currencyCodeToSymbol(t.currency)} ${formatStripeAmount(t.amount)}`,
<Typography variant='body2'>
{t.method}
</Typography>,
`${currencyCodeToSymbol(t.currency)} ${t.method === 'stripe' ? formatStripeAmount(t.amount) : t.amount}`,
moment(t.createdAt).format('LLL')
]) } || {}
}
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/profile/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,14 @@ class Profile extends Component {
component={ TransfersContainer }
/>
}
{ (this.props.user.Types && this.props.user.Types.map(t => t.name).includes('maintainer') ||
this.props.user.Types && this.props.user.Types.map(t => t.name).includes('contributor')) &&
<Route
exact
path='/profile/transfers/:transfer_id'
component={ TransfersContainer }
/>
}
{ (this.props.user.Types && this.props.user.Types.map(t => t.name).includes('contributor')) &&
<Route
exact
Expand Down
111 changes: 71 additions & 40 deletions frontend/src/components/profile/transfers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import MuiAlert from '@material-ui/lab/Alert'
import { messages } from '../task/messages/task-messages'
import CustomPaginationActionsTable from './transfer-table'
import TransferDetails from './transfers/transfer-detail'

const Alert = (props) => {
return <MuiAlert elevation={0} variant='standard' size='small' {...props} />
Expand Down Expand Up @@ -45,65 +46,73 @@ const styles = theme => ({
}
})

const Transfers = ({ searchTransfer, updateTransfer, transfers, user, intl, history }) => {
const Transfers = ({ searchTransfer, updateTransfer, fetchTransfer, transfers, transfer, user, intl, match, history }) => {
const [value, setValue] = React.useState(0)
const [openTransferDetail, setOpenTransferDetail] = React.useState(0)

const handleChange = (event, newValue) => {
setValue(newValue)
let getTransfers = () => {}
let getTransfers = () => { }
if (newValue === 'to') {
getTransfers = async () => await searchTransfer({ to: user.id })
}
if (newValue === 'from') {
getTransfers = async () => await searchTransfer({ userId: user.id })
}
getTransfers().then(t => {})
getTransfers().then(t => { })
}

const getTranfers = async () => await searchTransfer({ userId: user.id })

useEffect(() => {
setValue('from')
getTranfers().then(t => {})
getTranfers().then(t => { })
}, [user])

useEffect(() => {
const transferId = match?.params?.transfer_id
if (transferId) {
setOpenTransferDetail(transferId)
}
}, [match])

const transferActions = (t) => {
if(!user.account_id && t.status !== 'pending') return null
switch(value) {
if (!user.account_id && t.status !== 'pending') return null
switch (value) {
case 'from':
return (user.account_id && t.status === 'pending') ?
(<Button
size='small'
onClick={ async () => {
await updateTransfer({ id: t.id })
await searchTransfer({ userId: user.id })
} }
variant='contained'
color='secondary'
>
<FormattedMessage id='transfers.button.cancel' defaultMessage='Send bounty' />
</Button>
size='small'
onClick={async () => {
await updateTransfer({ id: t.id })
await searchTransfer({ userId: user.id })
}}
variant='contained'
color='secondary'
>
<FormattedMessage id='transfers.button.cancel' defaultMessage='Send bounty' />
</Button>
) : null
case 'to':
return ( user.account_id && t.status === 'pending') ?
return (user.account_id && t.status === 'pending') ?
<Button
size='small'
onClick={ async () => {
onClick={async () => {
await updateTransfer({ id: t.id })
await searchTransfer({ to: user.id })
} }
}}
variant='contained'
color='secondary'
disabled={ t.status !== 'pending'}
disabled={t.status !== 'pending'}
>
<FormattedMessage id='transfers.action.payout.button' defaultMessage='Request payout' />
</Button>
:
!user.account_id && <Button
size='small'
onClick={ () => {
onClick={() => {
history.push('/profile/user-account/details')
} }
}}
variant='contained'
color='secondary'
>
Expand All @@ -115,21 +124,27 @@ const Transfers = ({ searchTransfer, updateTransfer, transfers, user, intl, hist
}

return (
<div style={ { margin: '40px 0' } }>
<div style={{ margin: '40px 0' }}>
<TransferDetails user={user} history={history} id={openTransferDetail} fetchTransfer={fetchTransfer} transfer={transfer} open={openTransferDetail} onClose={() => {
setOpenTransferDetail(false)
history.push('/profile/transfers')
}
}
/>
<Container>
<Typography variant='h5' gutterBottom>
<FormattedMessage id='profile.transfer.title' defaultMessage='Transfers' />
</Typography>
{
!user.account_id && value === 'to' ?
<Alert
<Alert
severity='warning'
action={
<Button
size='small'
onClick={ () => {
onClick={() => {
history.push('/profile/user-account/details')
} }
}}
variant='contained'
color='secondary'
>
Expand All @@ -145,36 +160,52 @@ const Transfers = ({ searchTransfer, updateTransfer, transfers, user, intl, hist
null
}
<Tabs
value={ value }
onChange={ handleChange }
value={value}
onChange={handleChange}
indicatorColor='primary'
textColor='primary'
style={ { margin: '20px 0' } }
style={{ margin: '20px 0' }}
>
<Tab label={ intl.formatMessage(transferMessages.cardTableHeaderFrom) } value='from' />
<Tab label={ intl.formatMessage(transferMessages.cardTableHeaderTo) } value='to' />
<Tab label={intl.formatMessage(transferMessages.cardTableHeaderFrom)} value='from' />
<Tab label={intl.formatMessage(transferMessages.cardTableHeaderTo)} value='to' />
</Tabs>
<div>
<CustomPaginationActionsTable
tableHead={ [
tableHead={[
intl.formatMessage(messages.cardTableHeaderStatus),
intl.formatMessage(messages.cardTableHeaderMethod),
intl.formatMessage(messages.cardTableHeaderIssue),
intl.formatMessage(messages.cardTableHeaderValue),
intl.formatMessage(messages.cardTableHeaderCreated),
intl.formatMessage(messages.cardTableHeaderIssue),
intl.formatMessage(messages.cardTableHeaderActions)
] }
]}
transfers={
transfers && transfers.data && {
...transfers,
data: transfers.data.map(t => [
<Chip label={ t.status } />,
<Chip label={t.status} />,
<Typography variant='body2' gutterBottom>{t.transfer_method}</Typography>,
<a href={`/#/task/${t.Task.id}/${slugify(t.Task.title)}`}>
{t.Task.title}
</a>,
`$ ${t.value}`,
moment(t.createdAt).format('LLL'),
<a href={ `/#/task/${t.Task.id}/${slugify(t.Task.title)}` }>
{ t.Task.title }
</a>,
transferActions(t)
]) } || {}}
<>
{transferActions(t)}
<Button
size='small'
onClick={() => {
history.push(`/profile/transfers/${t.id}`)
}}
variant='contained'
color='secondary'
style={{ marginLeft: 10 }}
>
<FormattedMessage id='transfers.button.details' defaultMessage='Details' />
</Button>
</>
])
} || {}}
/>
</div>
</Container>
Expand Down
Loading

0 comments on commit af01487

Please sign in to comment.