import { TChangeStepSwap, TInitSwap, TChangeSwapProvider, ProviderName } from 'actions/swap'; import { NormalizedBityRates, NormalizedShapeshiftRates, NormalizedOptions, SwapInput } from 'reducers/swap/types'; import SimpleButton from 'components/ui/SimpleButton'; import { generateKindMax, generateKindMin, WhitelistedCoins, bityConfig } from 'config/bity'; import React, { PureComponent } from 'react'; import translate from 'translations'; import { combineAndUpper } from 'utils/formatters'; import { SwapDropdown } from 'components/ui'; import Spinner from 'components/ui/Spinner'; import { merge, reject, debounce } from 'lodash'; import './CurrencySwap.scss'; export interface StateProps { bityRates: NormalizedBityRates; shapeshiftRates: NormalizedShapeshiftRates; provider: ProviderName; options: NormalizedOptions; } export interface ActionProps { changeStepSwap: TChangeStepSwap; initSwap: TInitSwap; swapProvider: TChangeSwapProvider; } interface State { disabled: boolean; origin: SwapInput; destination: SwapInput; originKindOptions: any[]; destinationKindOptions: any[]; originErr: string; destinationErr: string; timeout: boolean; } type Props = StateProps & ActionProps; export default class CurrencySwap extends PureComponent { public state = { disabled: true, origin: { id: 'BTC', status: 'available', image: 'https://shapeshift.io/images/coins/bitcoin.png', amount: NaN } as SwapInput, destination: { id: 'ETH', status: 'available', image: 'https://shapeshift.io/images/coins/ether.png', amount: NaN } as SwapInput, originKindOptions: [], destinationKindOptions: [], originErr: '', destinationErr: '', timeout: false }; public debouncedCreateErrString = debounce((origin, destination, showError) => { const createErrString = ( originKind: WhitelistedCoins, amount: number, destKind: WhitelistedCoins ) => { const rate = this.getMinMax(originKind, destKind); let errString; if (amount > rate.max) { errString = `Maximum ${rate.max} ${originKind}`; } else { errString = `Minimum ${rate.min} ${originKind}`; } return errString; }; const originErr = showError ? createErrString(origin.id, origin.amount, destination.id) : ''; const destinationErr = showError ? createErrString(destination.id, destination.amount, origin.id) : ''; this.setErrorMessages(originErr, destinationErr); }, 1000); public componentDidMount() { setTimeout(() => { this.setState({ timeout: true }); }, 10000); const { origin } = this.state; const { options } = this.props; if (options.allIds && options.byId) { const originKindOptions: any[] = Object.values(options.byId); const destinationKindOptions: any[] = Object.values( reject(options.byId, o => o.id === origin.id) ); this.setState({ originKindOptions, destinationKindOptions }); } } public componentDidUpdate(prevProps: Props, prevState: State) { const { origin, destination } = this.state; const { options, bityRates, shapeshiftRates } = this.props; if (origin !== prevState.origin) { this.setDisabled(origin, destination); } const originCap = origin.id.toUpperCase(); const destCap = destination.id.toUpperCase(); const { provider } = this.props; const ensureCorrectProvider = (originCap === 'BTC' && destCap === 'ETH') || (destCap === 'BTC' && originCap === 'ETH'); const ensureBityRatesLoaded = bityRates.allIds.includes('ETHBTC') && bityRates.allIds.includes('BTCETH'); const ensureShapeshiftRatesLoaded = shapeshiftRates.allIds.length > 0; if (ensureBityRatesLoaded && ensureCorrectProvider) { if (provider === 'shapeshift') { this.props.swapProvider('bity'); } } else if (ensureShapeshiftRatesLoaded) { if (provider !== 'shapeshift') { this.props.swapProvider('shapeshift'); } } if (options.allIds !== prevProps.options.allIds && options.byId) { const originKindOptions: any[] = Object.values(options.byId); const destinationKindOptions: any[] = Object.values( reject(options.byId, o => o.id === origin.id) ); this.setState({ originKindOptions, destinationKindOptions }); } } public rateMixer = () => { const { shapeshiftRates, bityRates } = this.props; return merge(shapeshiftRates, bityRates); }; public getMinMax = (originKind: WhitelistedCoins, destinationKind) => { let min; let max; const { provider, bityRates } = this.props; if (provider === 'bity' && bityRates.allIds.length > 2) { if (originKind !== 'BTC') { const pairRate = this.rateMixer().byId['BTC' + originKind].rate; min = generateKindMin(pairRate, originKind); max = generateKindMax(pairRate, originKind); } else { min = bityConfig.BTCMin; max = bityConfig.BTCMax; } } else { const pair = (this.rateMixer() as NormalizedShapeshiftRates).byId[ originKind + destinationKind ]; min = pair.min; max = pair.limit; } return { min, max }; }; public isMinMaxValid = (originAmount: number, originKind: WhitelistedCoins, destinationKind) => { const rate = this.getMinMax(originKind, destinationKind); const higherThanMin = originAmount >= rate.min; const lowerThanMax = originAmount <= rate.max; return higherThanMin && lowerThanMax; }; public setDisabled(origin: SwapInput, destination: SwapInput) { this.clearErrMessages(); const amountsValid = origin.amount && destination.amount; const minMaxValid = this.isMinMaxValid(origin.amount as number, origin.id, destination.id); const disabled = !(amountsValid && minMaxValid); const showError = disabled && amountsValid; this.setState({ disabled }); this.debouncedCreateErrString(origin, destination, showError); } public setErrorMessages = (originErr, destinationErr) => { this.setState({ originErr, destinationErr }); }; public clearErrMessages = () => { this.setState({ originErr: '', destinationErr: '' }); }; public onClickStartSwap = () => { const { origin, destination } = this.state; const { changeStepSwap, initSwap } = this.props; initSwap({ origin, destination }); changeStepSwap(2); }; public setOriginAndDestinationToInitialVal = () => { this.setState({ origin: { ...this.state.origin, amount: NaN }, destination: { ...this.state.destination, amount: NaN } }); }; public updateOriginAmount = (origin: SwapInput, destination: SwapInput, amount: number) => { if (amount || amount === 0) { const pairName = combineAndUpper(origin.id, destination.id); const rate = this.rateMixer().byId[pairName].rate; const destinationAmount = amount * rate; this.setState({ origin: { ...this.state.origin, amount }, destination: { ...this.state.destination, amount: destinationAmount } }); } else { this.setOriginAndDestinationToInitialVal(); } }; public updateDestinationAmount = (origin: SwapInput, destination: SwapInput, amount: number) => { if (amount || amount === 0) { const pairNameReversed = combineAndUpper(destination.id, origin.id); const rate = this.rateMixer().byId[pairNameReversed].rate; const originAmount = amount * rate; this.setState({ origin: { ...this.state.origin, amount: originAmount }, destination: { ...this.state.destination, amount } }); } else { this.setOriginAndDestinationToInitialVal(); } }; public onChangeAmount = (event: React.FormEvent) => { const type = event.currentTarget.id; const { origin, destination } = this.state; const amount = parseFloat(event.currentTarget.value); type === 'origin-swap-input' ? this.updateOriginAmount(origin, destination, amount) : this.updateDestinationAmount(origin, destination, amount); }; public onChangeOriginKind = (newOption: WhitelistedCoins) => { const { origin, destination, destinationKindOptions } = this.state; const { options, initSwap } = this.props; const newOrigin = { ...origin, id: newOption, amount: '' }; const newDest = { id: newOption === destination.id ? origin.id : destination.id, amount: '' }; this.setState({ origin: newOrigin, destination: newDest, destinationKindOptions: reject( [...destinationKindOptions, options.byId[origin.id]], o => o.id === newOption ) }); initSwap({ origin: newOrigin, destination: newDest }); }; public onChangeDestinationKind = (newOption: WhitelistedCoins) => { const { initSwap } = this.props; const { origin, destination } = this.state; const newOrigin = { ...origin, amount: '' }; const newDest = { ...destination, id: newOption, amount: '' }; this.setState({ origin: newOrigin, destination: newDest }); initSwap({ origin: newOrigin, destination: newDest }); }; public render() { const { bityRates, shapeshiftRates, provider } = this.props; const { origin, destination, originKindOptions, destinationKindOptions, originErr, destinationErr, timeout } = this.state; const OriginKindDropDown = SwapDropdown as new () => SwapDropdown; const DestinationKindDropDown = SwapDropdown as new () => SwapDropdown; const pairName = combineAndUpper(origin.id, destination.id); const bityLoaded = bityRates.byId && bityRates.byId[pairName] ? true : false; const shapeshiftLoaded = shapeshiftRates.byId && shapeshiftRates.byId[pairName] ? true : false; // This ensures both are loaded const loaded = provider === 'shapeshift' ? shapeshiftLoaded : bityLoaded && shapeshiftLoaded; const timeoutLoaded = (bityLoaded && timeout) || (shapeshiftLoaded && timeout); return (

{translate('SWAP_init_1')}

{loaded || timeoutLoaded ? (
{originErr && {originErr}}

{translate('SWAP_init_2')}

{destinationErr && ( {destinationErr} )}
) : ( )}
); } }