Skip to content

Commit

Permalink
feat(coinify check order review. other misc coinify improvements)
Browse files Browse the repository at this point in the history
  • Loading branch information
sixtedemaupeou committed May 31, 2018
1 parent 75a8c62 commit 56937a7
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const CheckBoxInput = props => {

CheckBoxInput.propTypes = {
name: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
children: PropTypes.node,
checked: PropTypes.bool,
disabled: PropTypes.string
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const selectStyle = status => {
case 'rejected': return { color: 'error' }
case 'failed': return { color: 'error' }
case 'cancelled': return { color: 'error' }
case 'expired': return { color: 'error' }
}
}

Expand All @@ -19,7 +20,6 @@ const renderStatus = (status, isBuy) => {
switch (status) {
case 'awaiting_transfer_in':
case 'processing': return isBuy ? <FormattedMessage id='scenes.buysellorderhistory.list.orderstatus.processingbuy' defaultMessage='Pending Buy' /> : <FormattedMessage id='scenes.buysellorderhistory.list.orderstatus.processingsell' defaultMessage='Pending Sell' />

case 'completed': return isBuy ? <FormattedMessage id='scenes.buysellorderhistory.list.orderstatus.completedbuy' defaultMessage='Completed Buy' /> : <FormattedMessage id='scenes.buysellorderhistory.list.orderstatus.completedsell' defaultMessage='Completed Sell' />
case 'rejected': return <FormattedMessage id='scenes.buysellorderhistory.list.orderstatus.rejected' defaultMessage='Rejected' />
case 'failed': return <FormattedMessage id='scenes.buysellorderhistory.list.orderstatus.failed' defaultMessage='Failed' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,13 @@ export const EmailHelper = styled.span`
color: ${props => props.theme['brand-secondary']};
}
`

export const CancelWrapper = styled.div`
export const CenteredWrapper = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 15px;
`
export const CancelWrapper = styled(CenteredWrapper)`
a {
color: #545456;
font-weight: 300;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ const Wrapper = styled.div`
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
`
const Container = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 25px;
`
const Error = styled(Text)`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React, { Fragment } from 'react'
import styled from 'styled-components'
import { FormattedMessage } from 'react-intl'
import { prop } from 'ramda'
import { any, equals, prop } from 'ramda'
import moment from 'moment'

import { ModalHeader, ModalBody, Text, Button } from 'blockchain-info-components'
import { OrderDetailsTable, OrderDetailsRow } from 'components/BuySell/OrderDetails'
import { tradeDetails, statusHelper, bodyStatusHelper } from 'services/CoinifyService'
import { spacing } from 'services/StyleService'

const TableTitle = styled(Text)`
padding-top: 10px;
`
const ButtonRow = styled.div`
display: flex;
flex-direction: row;
Expand All @@ -25,6 +27,7 @@ const Trade = ({ trade, close }) => {
const bodyStatus = bodyStatusHelper(trade.state, trade.isBuy)
const details = tradeDetails.renderDetails(trade)
const date = moment(prop('createdAt', trade)).local().format('MMMM D YYYY @ h:mm A')
const isPendingSell = any(equals(prop('state', trade)))(['awaiting_transfer_in', 'processing']) && !prop('isBuy', trade)

return (
<Fragment>
Expand All @@ -37,7 +40,8 @@ const Trade = ({ trade, close }) => {
<Text size='13px' weight={300}>
{ bodyStatus.text }
</Text>
<StyledOrderDetailsTable style={spacing('mt-10')}>
<TableTitle size='13px' weight={400}><FormattedMessage id='orderdetails.payoutdetails' defaultMessage='Order Details' /></TableTitle>
<StyledOrderDetailsTable>
<OrderDetailsRow>
<Text size='13px' weight={300}><FormattedMessage id='orderdetails.coinifytradeid' defaultMessage='Coinify Trade ID' /></Text>
<Text size='13px' weight={300}>{prop('id', trade)}</Text>
Expand All @@ -55,7 +59,7 @@ const Trade = ({ trade, close }) => {
<Text size='13px' weight={300}>{prop('btcAmount', details)}</Text>
</OrderDetailsRow>
</StyledOrderDetailsTable>
<Text size='13px' weight={300}><FormattedMessage id='orderdetails.payoutdetails' defaultMessage='Payout Details' /></Text>
<TableTitle size='13px' weight={400}><FormattedMessage id='orderdetails.payoutdetails' defaultMessage='Payout Details' /></TableTitle>
<StyledOrderDetailsTable>
{
!trade.isBuy &&
Expand All @@ -73,6 +77,12 @@ const Trade = ({ trade, close }) => {
<Text size='13px' weight={300} color='success'>{prop('total', details)}</Text>
</OrderDetailsRow>
</StyledOrderDetailsTable>
{
isPendingSell &&
<Text size='12px' weight={300}>
<FormattedMessage id='orderdetails.footnote' defaultMessage='*Please note: depending on your bank’s tranfers policies, you will see the funds reflected in your account within 1-2 days from the transfer. ' />
</Text>
}
<ButtonRow>
<Button width='100px' onClick={close} nature='primary'>
<FormattedMessage id='close' defaultMessage='Close' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const CoinifyBuy = props => {
quoteR={buyQuoteR}
onSubmit={initiateBuy}
busy={busy}
type={'buy'}
clearTradeError={clearTradeError}
/>
</OrderSubmitWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, { Fragment } from 'react'
import styled from 'styled-components'
import { Text, Button, HeartbeatLoader, Link } from 'blockchain-info-components'
import { Remote } from 'blockchain-wallet-v4/src'
import { FormattedMessage } from 'react-intl'

import { Text, Link } from 'blockchain-info-components'
import FaqRow from 'components/Faq/FaqRow'
import CountdownTimer from 'components/Form/CountdownTimer'
import { Wrapper as ExchangeCheckoutWrapper } from '../../ExchangeCheckout'
import { spacing } from 'services/StyleService'
import { reviewOrder, currencySymbolMap } from 'services/CoinifyService'
import { FormattedMessage } from 'react-intl'
import { OrderDetailsTable, OrderDetailsRow } from 'components/BuySell/OrderDetails'
import { BorderBox, CancelWrapper, Row } from 'components/BuySell/Signup'
import { BorderBox, Row } from 'components/BuySell/Signup'
import { StepTransition } from 'components/Utilities/Stepper'
import ReviewForm from './ReviewForm'

const ExchangeRateWrapper = styled.div`
display: flex;
Expand All @@ -29,110 +29,103 @@ const renderRate = (rate, q) => {
values={{ rate: `${currencySymbolMap[q.baseCurrency]}${rate.toLocaleString()}` }} />
}

export const OrderDetails = ({ quoteR, onRefreshQuote, type, medium }) => (
<Row>
<BorderBox>
<Text size='32px' weight={600} style={spacing('mb-10')}>
<FormattedMessage id='buy.almost_there' defaultMessage="You're almost there" />
</Text>
<Text size='14px' weight={300} style={spacing('mb-20')}>
<FormattedMessage id='buy.review_order_subtext'
defaultMessage='Before we can start processing your order, review the order details below. If everything looks good to you, click submit to complete your order.' />
</Text>
<ExchangeRateWrapper>
<Text size='12px' weight={500} style={spacing('mr-10')}>
<FormattedMessage id='exchange_rate' defaultMessage='Exchange Rate' />
export const OrderDetails = (props) => {
const { quoteR, onRefreshQuote, type, medium } = props

return (
<Row>
<BorderBox>
<Text size='32px' weight={600} style={spacing('mb-10')}>
<FormattedMessage id='buy.almost_there' defaultMessage="You're almost there" />
</Text>
<Text size='12px' weight={300}>
1 BTC = {quoteR.map((q) => {
const rate = +((1 / (Math.abs(q.quoteAmount) / 1e8)) * Math.abs(q.baseAmount)).toFixed(2)
return renderRate(rate, q)
}).getOrElse('~')}
<Text size='14px' weight={300} style={spacing('mb-20')}>
<FormattedMessage id='buy.review_order_subtext'
defaultMessage='Before we can start processing your order, review the order details below. If everything looks good to you, click submit to complete your order.' />
</Text>
</ExchangeRateWrapper>
<OrderDetailsTable style={spacing('mt-10')}>
<OrderDetailsRow>
{
type === 'buy'
? <Text size='13px' weight={300}><FormattedMessage id='orderdetails.amounttopurchase' defaultMessage='BTC Amount to Purchase' /></Text>
: <Text size='13px' weight={300}><FormattedMessage id='orderdetails.amounttosell' defaultMessage='BTC Amount to Sell' /></Text>
}
<Text size='13px' weight={300}>{quoteR.map(q => reviewOrder.renderSummary(q, type, medium)).data.firstRow}</Text>
</OrderDetailsRow>
<OrderDetailsRow>
<Text size='13px' weight={300}><FormattedMessage id='orderdetails.tradingfee' defaultMessage='Trading Fee' /></Text>
<Text size='13px' weight={300}>{quoteR.map(q => reviewOrder.renderSummary(q, type, medium)).data.fee}</Text>
</OrderDetailsRow>
<OrderDetailsRow>
{
type === 'buy'
? <Text size='13px' weight={300}><FormattedMessage id='orderdetails.totalcost' defaultMessage='Total Cost' /></Text>
: <Text size='13px' weight={300}><FormattedMessage id='orderdetails.totaltobereceived' defaultMessage='Total to be Received' /></Text>
}
<Text size='13px' weight={300} color='success'>{quoteR.map(q => reviewOrder.renderSummary(q, type, medium)).data.total}</Text>
</OrderDetailsRow>
</OrderDetailsTable>
{quoteR.map((q) => (
<CountdownTimer
style={spacing('mt-20')}
expiryDate={q.expiresAt.getTime()}
handleExpiry={onRefreshQuote}
tooltipExpiryTime='15 minutes'
/>
)).getOrElse(null)}
</BorderBox>
</Row>
)

export const OrderSubmit = ({ quoteR, onSubmit, busy, clearTradeError, goToStep }) => (
<Fragment>
{
busy.error
? <div onClick={() => clearTradeError()}>
<Text weight={300} color='error' size='13px' style={spacing('mb-5')}>
Sorry, something went wrong with your trade: { busy.error_description }
<ExchangeRateWrapper>
<Text size='12px' weight={500} style={spacing('mr-10')}>
<FormattedMessage id='exchange_rate' defaultMessage='Exchange Rate' />
</Text>
<Text size='12px' weight={300}>
1 BTC = {quoteR.map((q) => {
const rate = +((1 / (Math.abs(q.quoteAmount) / 1e8)) * Math.abs(q.baseAmount)).toFixed(2)
return renderRate(rate, q)
}).getOrElse('~')}
</Text>
<span>
<StepTransition restart Component={Link} weight={300} size='13px'>
<FormattedMessage id='try_again' defaultMessage='Try again' />
</StepTransition>
</span>
</div>
: <Fragment>
<Button
nature='primary'
disabled={!Remote.Success.is(quoteR) || busy}
onClick={onSubmit}>
</ExchangeRateWrapper>
<OrderDetailsTable style={spacing('mt-10')}>
<OrderDetailsRow>
{
busy
? <HeartbeatLoader height='20px' width='20px' color='white' />
: <FormattedMessage id='submit' defaultMessage='Submit' />
type === 'buy'
? <Text size='13px' weight={300}><FormattedMessage id='orderdetails.amounttopurchase' defaultMessage='BTC Amount to Purchase' /></Text>
: <Text size='13px' weight={300}><FormattedMessage id='orderdetails.amounttosell' defaultMessage='BTC Amount to Sell' /></Text>
}
</Button>
<CancelWrapper>
<StepTransition restart Component={Link}>
<FormattedMessage id='cancel' defaultMessage='Cancel' />
</StepTransition>
</CancelWrapper>
</Fragment>
}
<StyledFaqRow
title={<FormattedMessage id='faq.how_long_to_receive_q' defaultMessage='How long does it take to get my funds?' />}
description={
<div>
<FormattedMessage id='faq.how_long_to_receive_a1' defaultMessage='The quote expires within 15 minutes of placing the order. If the transaction is not broadcasted during that time the order will not be processed.' />
<FormattedMessage id='faq.how_long_to_receive_a2' defaultMessage='Coinify will contact you with intructions on how to receive a BTC refund if they are received after the quote expires, and if the amount received is higher or lower that the one specified in the order.' />
<FormattedMessage id='faq.how_long_to_receive_a3' defaultMessage='Coinify won’t be refunding the bitcoin transaction fee.' />
</div>
<Text size='13px' weight={300}>{quoteR.map(q => reviewOrder.renderSummary(q, type, medium)).data.firstRow}</Text>
</OrderDetailsRow>
<OrderDetailsRow>
<Text size='13px' weight={300}><FormattedMessage id='orderdetails.tradingfee' defaultMessage='Trading Fee' /></Text>
<Text size='13px' weight={300}>{quoteR.map(q => reviewOrder.renderSummary(q, type, medium)).data.fee}</Text>
</OrderDetailsRow>
<OrderDetailsRow>
{
type === 'buy'
? <Text size='13px' weight={300}><FormattedMessage id='orderdetails.totalcost' defaultMessage='Total Cost' /></Text>
: <Text size='13px' weight={300}><FormattedMessage id='orderdetails.totaltobereceived' defaultMessage='Total to be Received' /></Text>
}
<Text size='13px' weight={300} color='success'>{quoteR.map(q => reviewOrder.renderSummary(q, type, medium)).data.total}</Text>
</OrderDetailsRow>
</OrderDetailsTable>
{quoteR.map((q) => (
<CountdownTimer
style={spacing('mt-20')}
expiryDate={q.expiresAt.getTime()}
handleExpiry={onRefreshQuote}
tooltipExpiryTime='15 minutes'
/>
)).getOrElse(null)}
</BorderBox>
</Row>
)
}

export const OrderSubmit = (props) => {
const { busy, clearTradeError, onSubmit, quoteR, type } = props

return (
<Fragment>
{
busy.error
? <div onClick={() => clearTradeError()}>
<Text weight={300} color='error' size='13px' style={spacing('mb-5')}>
<FormattedMessage id='scenes.buysell.orderreview.wrong' defaultMessage='Sorry, something went wrong with your trade:' />
{busy.error_description}
</Text>
<span>
<StepTransition restart Component={Link} weight={300} size='13px'>
<FormattedMessage id='try_again' defaultMessage='Try again' />
</StepTransition>
</span>
</div>
: <ReviewForm busy={busy} onSubmit={onSubmit} quoteR={quoteR} type={type} />
}
/>
<StyledFaqRow
title={<FormattedMessage id='faq.exchange_rate_q' defaultMessage='What is the exchange rate?' />}
description={<FormattedMessage id='faq.exchange_rate_a' defaultMessage='The exchange rate varies from minute to minute.' />}
/>
<StyledFaqRow
title={<FormattedMessage id='faq.exchange_fees_q' defaultMessage='What are the fees?' />}
description={<FormattedMessage id='faq.exchange_fees_a' defaultMessage='Each exchange takes a small percentage of the total amount as a fee.' />}
/>
</Fragment>
)
<StyledFaqRow
title={<FormattedMessage id='faq.how_long_to_receive_q' defaultMessage='How long does it take to get my funds?' />}
description={
<div>
<FormattedMessage id='faq.how_long_to_receive_a1' defaultMessage='The quote expires within 15 minutes of placing the order. If the transaction is not broadcasted during that time the order will not be processed.' />
<FormattedMessage id='faq.how_long_to_receive_a2' defaultMessage='Coinify will contact you with intructions on how to receive a BTC refund if they are received after the quote expires, and if the amount received is higher or lower that the one specified in the order.' />
<FormattedMessage id='faq.how_long_to_receive_a3' defaultMessage='Coinify won’t be refunding the bitcoin transaction fee.' />
</div>
}
/>
<StyledFaqRow
title={<FormattedMessage id='faq.exchange_rate_q' defaultMessage='What is the exchange rate?' />}
description={<FormattedMessage id='faq.exchange_rate_a' defaultMessage='The exchange rate varies from minute to minute.' />}
/>
<StyledFaqRow
title={<FormattedMessage id='faq.exchange_fees_q' defaultMessage='What are the fees?' />}
description={<FormattedMessage id='faq.exchange_fees_a' defaultMessage='Each exchange takes a small percentage of the total amount as a fee.' />}
/>
</Fragment>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import { FormattedMessage } from 'react-intl'
import { equals } from 'ramda'
import { Button, HeartbeatLoader, Link, Text } from 'blockchain-info-components'
import { Remote } from 'blockchain-wallet-v4/src'

import { CancelWrapper, CenteredWrapper, Row } from 'components/BuySell/Signup'
import { CheckBox } from 'components/Form'
import { StepTransition } from 'components/Utilities/Stepper'
import { required } from 'services/FormHelper'

const ReviewForm = (props) => {
const { invalid, submitting, busy, onSubmit, quoteR, type } = props
const isSell = equals(type, 'sell')
return (
<form>
{
isSell &&
<Row>
<Field name='coinifycheckbox' component={CheckBox} validate={[required]} />
<Text size='12px' width={300}>
<FormattedMessage id='scenes.buysell.coinify.sell.orderreview.checkboxtext' defaultMessage='I accept that Coinify will process my order upon completion of the bitcoin transaction, and that bitcoin will be traded at the available exchange rate at the time, which may differ from the displayed rate.' />
</Text>
</Row>
}
<CenteredWrapper>
<Button
nature='primary'
disabled={submitting || invalid || !Remote.Success.is(quoteR) || busy}
onClick={onSubmit}>
{
busy
? <HeartbeatLoader height='20px' width='20px' color='white' />
: <FormattedMessage id='submit' defaultMessage='Submit' />
}
</Button>
</CenteredWrapper>
<CancelWrapper>
<StepTransition restart Component={Link}>
<FormattedMessage id='cancel' defaultMessage='Cancel' />
</StepTransition>
</CancelWrapper>
</form>
)
}

export default reduxForm({ form: 'reviewForm' })(ReviewForm)

0 comments on commit 56937a7

Please sign in to comment.