You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
9.9 KiB
368 lines
9.9 KiB
import React from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { Box, Flex } from 'rebass'
|
|
import { FormattedMessage, injectIntl } from 'react-intl'
|
|
import { convert } from 'lib/utils/btc'
|
|
import {
|
|
Bar,
|
|
Button,
|
|
CryptoAmountInput,
|
|
Dropdown,
|
|
FiatAmountInput,
|
|
Form,
|
|
Header,
|
|
Label,
|
|
Panel,
|
|
Text,
|
|
TextArea
|
|
} from 'components/UI'
|
|
import Lightning from 'components/Icon/Lightning'
|
|
import { RequestSummary } from '.'
|
|
import messages from './messages'
|
|
|
|
/**
|
|
* Request form.
|
|
*/
|
|
class Request extends React.Component {
|
|
state = {
|
|
currentStep: 'form'
|
|
}
|
|
|
|
static propTypes = {
|
|
/** Human readable chain name */
|
|
cryptoName: PropTypes.string.isRequired,
|
|
/** Current ticker data as provided by blockchain.info */
|
|
currentTicker: PropTypes.object.isRequired,
|
|
/** Currently selected cryptocurrency (key). */
|
|
cryptoCurrency: PropTypes.string.isRequired,
|
|
/** Ticker symbol of the currently selected cryptocurrency. */
|
|
cryptoCurrencyTicker: PropTypes.string.isRequired,
|
|
/** List of supported cryptocurrencies. */
|
|
cryptoCurrencies: PropTypes.arrayOf(
|
|
PropTypes.shape({
|
|
key: PropTypes.string.isRequired,
|
|
name: PropTypes.string.isRequired
|
|
})
|
|
).isRequired,
|
|
/** List of supported fiat currencies. */
|
|
fiatCurrencies: PropTypes.array.isRequired,
|
|
/** Currently selected fiat currency (key). */
|
|
fiatCurrency: PropTypes.string.isRequired,
|
|
/** Boolean indicating wether the form is being processed. If true, form buttons are disabled. */
|
|
isProcessing: PropTypes.bool,
|
|
/** Boolean indicating wether the invoice has already been paid. */
|
|
isPaid: PropTypes.bool,
|
|
/** Lightning Payment request. */
|
|
payReq: PropTypes.string,
|
|
/** Set the current cryptocurrency. */
|
|
setCryptoCurrency: PropTypes.func.isRequired,
|
|
/** Set the current fiat currency */
|
|
setFiatCurrency: PropTypes.func.isRequired,
|
|
/** Create an invoice using the supplied details */
|
|
createInvoice: PropTypes.func.isRequired
|
|
}
|
|
|
|
static defaultProps = {
|
|
isProcessing: false,
|
|
isPaid: false,
|
|
payReq: null
|
|
}
|
|
|
|
amountInput = React.createRef()
|
|
|
|
componentDidMount() {
|
|
this.focusAmountInput()
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
const { payReq } = this.props
|
|
const { currentStep } = this.state
|
|
if (payReq !== prevProps.payReq && currentStep === 'form') {
|
|
this.nextStep()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Liost of enabled form steps.
|
|
*/
|
|
steps = () => {
|
|
return ['form', 'summary']
|
|
}
|
|
|
|
/**
|
|
* Go back to previous form step.
|
|
*/
|
|
previousStep = () => {
|
|
const { currentStep } = this.state
|
|
const nextStep = Math.max(this.steps().indexOf(currentStep) - 1, 0)
|
|
if (currentStep !== nextStep) {
|
|
this.setState({ currentStep: this.steps()[nextStep] })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Progress to next form step.
|
|
*/
|
|
nextStep = () => {
|
|
const { currentStep } = this.state
|
|
const nextStep = Math.min(this.steps().indexOf(currentStep) + 1, this.steps().length - 1)
|
|
if (currentStep !== nextStep) {
|
|
this.setState({ currentStep: this.steps()[nextStep] })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Form submit handler.
|
|
* @param {Object} values submitted form values.
|
|
*/
|
|
onSubmit = values => {
|
|
const { cryptoCurrency, createInvoice } = this.props
|
|
createInvoice(values.amountCrypto, cryptoCurrency, values.memo)
|
|
}
|
|
|
|
/**
|
|
* Store the formApi on the component context to make it available at this.formApi.
|
|
*/
|
|
setFormApi = formApi => {
|
|
this.formApi = formApi
|
|
}
|
|
|
|
/**
|
|
* Focus the amount input.
|
|
*/
|
|
focusAmountInput = () => {
|
|
if (this.amountInput.current) {
|
|
this.amountInput.current.focus()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* set the amountFiat field whenever the crypto amount changes.
|
|
*/
|
|
handleAmountCryptoChange = e => {
|
|
const { cryptoCurrency, currentTicker, fiatCurrency } = this.props
|
|
const lastPrice = currentTicker[fiatCurrency].last
|
|
const value = convert(cryptoCurrency, 'fiat', e.target.value, lastPrice)
|
|
this.formApi.setValue('amountFiat', value)
|
|
}
|
|
|
|
/**
|
|
* set the amountCrypto field whenever the fiat amount changes.
|
|
*/
|
|
handleAmountFiatChange = e => {
|
|
const { cryptoCurrency, currentTicker, fiatCurrency } = this.props
|
|
const lastPrice = currentTicker[fiatCurrency].last
|
|
const value = convert('fiat', cryptoCurrency, e.target.value, lastPrice)
|
|
this.formApi.setValue('amountCrypto', value)
|
|
}
|
|
|
|
/**
|
|
* Handle changes from the crypto currency dropdown.
|
|
*/
|
|
handleCryptoCurrencyChange = value => {
|
|
const { setCryptoCurrency } = this.props
|
|
setCryptoCurrency(value)
|
|
}
|
|
|
|
/**
|
|
* Handle changes from the fiat currency dropdown.
|
|
*/
|
|
handleFiatCurrencyChange = value => {
|
|
const { setFiatCurrency } = this.props
|
|
setFiatCurrency(value)
|
|
}
|
|
|
|
renderHelpText = () => {
|
|
return (
|
|
<Box mb={4}>
|
|
<Text textAlign="justify">
|
|
<FormattedMessage {...messages.description} />
|
|
</Text>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
renderAmountFields = () => {
|
|
const {
|
|
cryptoCurrency,
|
|
cryptoCurrencies,
|
|
currentTicker,
|
|
fiatCurrency,
|
|
fiatCurrencies
|
|
} = this.props
|
|
|
|
return (
|
|
<Box>
|
|
<Label htmlFor="amountCrypto" pb={2}>
|
|
<FormattedMessage {...messages.amount} />
|
|
</Label>
|
|
|
|
<Flex justifyContent="space-between" alignItems="flex-start" mb={3}>
|
|
<Flex width={6 / 13}>
|
|
<Box width={150}>
|
|
<CryptoAmountInput
|
|
field="amountCrypto"
|
|
name="amountCrypto"
|
|
currency={cryptoCurrency}
|
|
required
|
|
width={150}
|
|
validateOnChange
|
|
validateOnBlur
|
|
onChange={this.handleAmountCryptoChange}
|
|
forwardedRef={this.amountInput}
|
|
/>
|
|
</Box>
|
|
<Dropdown
|
|
activeKey={cryptoCurrency}
|
|
items={cryptoCurrencies}
|
|
onChange={this.handleCryptoCurrencyChange}
|
|
mt={3}
|
|
ml={2}
|
|
/>
|
|
</Flex>
|
|
<Text textAlign="center" mt={3} width={1 / 11}>
|
|
=
|
|
</Text>
|
|
<Flex width={6 / 13}>
|
|
<Box width={150} ml="auto">
|
|
<FiatAmountInput
|
|
field="amountFiat"
|
|
name="amountFiat"
|
|
currency={fiatCurrency}
|
|
currentTicker={currentTicker}
|
|
width={150}
|
|
onChange={this.handleAmountFiatChange}
|
|
/>
|
|
</Box>
|
|
|
|
<Dropdown
|
|
activeKey={fiatCurrency}
|
|
items={fiatCurrencies}
|
|
onChange={this.handleFiatCurrencyChange}
|
|
mt={3}
|
|
ml={2}
|
|
/>
|
|
</Flex>
|
|
</Flex>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
renderMemoField = () => {
|
|
const { intl } = this.props
|
|
return (
|
|
<Box>
|
|
<Box pb={2}>
|
|
<Label htmlFor="memo">
|
|
<FormattedMessage {...messages.memo} />
|
|
</Label>
|
|
</Box>
|
|
|
|
<TextArea
|
|
field="memo"
|
|
name="memo"
|
|
validateOnBlur
|
|
validateOnChange
|
|
placeholder={intl.formatMessage({ ...messages.memo_placeholder })}
|
|
width={1}
|
|
rows={3}
|
|
css={{ resize: 'vertical', 'min-height': '48px' }}
|
|
/>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Form renderer.
|
|
*/
|
|
render() {
|
|
const {
|
|
createInvoice,
|
|
cryptoCurrency,
|
|
cryptoCurrencyTicker,
|
|
cryptoCurrencies,
|
|
currentTicker,
|
|
cryptoName,
|
|
fiatCurrencies,
|
|
fiatCurrency,
|
|
intl,
|
|
isProcessing,
|
|
isPaid,
|
|
payReq,
|
|
setCryptoCurrency,
|
|
setFiatCurrency,
|
|
...rest
|
|
} = this.props
|
|
const { currentStep } = this.state
|
|
return (
|
|
<Form
|
|
width={1}
|
|
css={{ height: '100%' }}
|
|
{...rest}
|
|
getApi={this.setFormApi}
|
|
onSubmit={this.onSubmit}
|
|
>
|
|
{({ formState }) => {
|
|
// Determine what the text should be for the next button.
|
|
let nextButtonText = intl.formatMessage({ ...messages.button_text })
|
|
if (formState.values.amountCrypto) {
|
|
nextButtonText = `${intl.formatMessage({
|
|
...messages.button_text
|
|
})} ${formState.values.amountCrypto} ${cryptoCurrencyTicker}`
|
|
}
|
|
|
|
return (
|
|
<Panel>
|
|
<Panel.Header>
|
|
<Header
|
|
title={`${intl.formatMessage({
|
|
...messages.title
|
|
})} ${cryptoName} (${cryptoCurrencyTicker})`}
|
|
subtitle={<FormattedMessage {...messages.subtitle} />}
|
|
logo={<Lightning height="45px" width="45px" />}
|
|
/>
|
|
</Panel.Header>
|
|
<Bar />
|
|
<Panel.Body>
|
|
{currentStep == 'form' ? (
|
|
<React.Fragment>
|
|
{this.renderHelpText()}
|
|
{this.renderAmountFields()}
|
|
{this.renderMemoField()}
|
|
</React.Fragment>
|
|
) : (
|
|
<RequestSummary
|
|
mt={-3}
|
|
// State
|
|
cryptoCurrency={cryptoCurrency}
|
|
cryptoCurrencies={cryptoCurrencies}
|
|
currentTicker={currentTicker}
|
|
payReq={payReq}
|
|
isPaid={isPaid}
|
|
// Dispatch
|
|
setCryptoCurrency={setCryptoCurrency}
|
|
setFiatCurrency={setFiatCurrency}
|
|
/>
|
|
)}
|
|
</Panel.Body>
|
|
{currentStep == 'form' && (
|
|
<Panel.Footer>
|
|
<Button
|
|
type="submit"
|
|
disabled={formState.pristine || formState.invalid || isProcessing}
|
|
processing={isProcessing}
|
|
mx="auto"
|
|
>
|
|
{nextButtonText}
|
|
</Button>
|
|
</Panel.Footer>
|
|
)}
|
|
</Panel>
|
|
)
|
|
}}
|
|
</Form>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default injectIntl(Request)
|
|
|