Remove swap #7659
17 changed files with 22 additions and 1086 deletions
|
@ -1,27 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import WalletSwap from './view';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doAddCoinSwap, doQueryCoinSwapStatus } from 'redux/actions/coinSwap';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { selectCoinSwaps } from 'redux/selectors/coinSwap';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { doGetNewAddress, doCheckAddressIsMine } from 'redux/actions/wallet';
|
||||
import { selectReceiveAddress } from 'redux/selectors/wallet';
|
||||
|
||||
const select = (state, props) => ({
|
||||
receiveAddress: selectReceiveAddress(state),
|
||||
coinSwaps: selectCoinSwaps(state),
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
doToast: (options) => dispatch(doToast(options)),
|
||||
addCoinSwap: (coinSwap) => dispatch(doAddCoinSwap(coinSwap)),
|
||||
getNewAddress: () => dispatch(doGetNewAddress()),
|
||||
checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)),
|
||||
queryCoinSwapStatus: (sendAddress) => dispatch(doQueryCoinSwapStatus(sendAddress)),
|
||||
});
|
||||
|
||||
export default withRouter(connect(select, perform)(WalletSwap));
|
|
@ -1,707 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import Card from 'component/common/card';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import Spinner from 'component/spinner';
|
||||
import Nag from 'component/common/nag';
|
||||
import CopyableText from 'component/copyableText';
|
||||
import Icon from 'component/common/icon';
|
||||
import QRCode from 'component/common/qr-code';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import { clipboard } from 'electron';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import { Redirect, useHistory } from 'react-router';
|
||||
|
||||
const ENABLE_ALTERNATIVE_COINS = true;
|
||||
|
||||
const BTC_SATOSHIS = 100000000;
|
||||
const LBC_MAX = 21000000;
|
||||
const LBC_MIN = 1;
|
||||
|
||||
const IS_DEV = process.env.NODE_ENV !== 'production';
|
||||
const DEBOUNCE_BTC_CHANGE_MS = 400;
|
||||
|
||||
const INTERNAL_APIS_DOWN = 'internal_apis_down';
|
||||
const BTC_API_STATUS_PENDING = 'NEW'; // Started swap, waiting for coin.
|
||||
const BTC_API_STATUS_CONFIRMING = 'PENDING'; // Coin receiving, waiting confirmation.
|
||||
const BTC_API_STATUS_PROCESSING = 'COMPLETED'; // Coin confirmed. Sending LBC.
|
||||
const BTC_API_STATUS_UNRESOLVED = 'UNRESOLVED'; // Underpaid, overpaid, etc.
|
||||
const BTC_API_STATUS_EXPIRED = 'EXPIRED'; // Charge expired (60 minutes).
|
||||
const BTC_API_STATUS_ERROR = 'Error';
|
||||
|
||||
const ACTION_MAIN = 'action_main';
|
||||
const ACTION_STATUS_PENDING = 'action_pending';
|
||||
const ACTION_STATUS_CONFIRMING = 'action_confirming';
|
||||
const ACTION_STATUS_PROCESSING = 'action_processing';
|
||||
const ACTION_STATUS_SUCCESS = 'action_success';
|
||||
const ACTION_PAST_SWAPS = 'action_past_swaps';
|
||||
|
||||
const NAG_API_STATUS_PENDING = 'Waiting to receive your crypto.';
|
||||
const NAG_API_STATUS_CONFIRMING = 'Confirming transaction.';
|
||||
const NAG_API_STATUS_PROCESSING = 'Crypto received. Sending your Credits.';
|
||||
const NAG_API_STATUS_SUCCESS = 'Credits sent. You should see it in your wallet.';
|
||||
const NAG_API_STATUS_ERROR = 'An error occurred on the previous swap.';
|
||||
const NAG_SWAP_CALL_FAILED = 'Failed to initiate swap.';
|
||||
// const NAG_STATUS_CALL_FAILED = 'Failed to query swap status.';
|
||||
const NAG_SERVER_DOWN = 'The system is currently down. Come back later.';
|
||||
const NAG_RATE_CALL_FAILED = 'Unable to obtain exchange rate. Try again later.';
|
||||
const NAG_EXPIRED = 'Swap expired.';
|
||||
|
||||
type Props = {
|
||||
receiveAddress: string,
|
||||
coinSwaps: Array<CoinSwapInfo>,
|
||||
isAuthenticated: boolean,
|
||||
doToast: ({ message: string }) => void,
|
||||
addCoinSwap: (CoinSwapInfo) => void,
|
||||
getNewAddress: () => void,
|
||||
checkAddressIsMine: (string) => void,
|
||||
openModal: (string, {}) => void,
|
||||
queryCoinSwapStatus: (string) => void,
|
||||
};
|
||||
|
||||
function WalletSwap(props: Props) {
|
||||
const {
|
||||
receiveAddress,
|
||||
doToast,
|
||||
coinSwaps,
|
||||
isAuthenticated,
|
||||
addCoinSwap,
|
||||
getNewAddress,
|
||||
checkAddressIsMine,
|
||||
openModal,
|
||||
queryCoinSwapStatus,
|
||||
} = props;
|
||||
|
||||
const [btc, setBtc] = React.useState(0);
|
||||
const [lbcError, setLbcError] = React.useState();
|
||||
const [lbc, setLbc] = usePersistedState('swap-desired-lbc', LBC_MIN);
|
||||
const [action, setAction] = React.useState(ACTION_MAIN);
|
||||
const [nag, setNag] = React.useState(null);
|
||||
const [showQr, setShowQr] = React.useState(false);
|
||||
const [isFetchingRate, setIsFetchingRate] = React.useState(false);
|
||||
const [isSwapping, setIsSwapping] = React.useState(false);
|
||||
const [isRefreshingStatus, setIsRefreshingStatus] = React.useState(false);
|
||||
const { location } = useHistory();
|
||||
const [swap, setSwap] = React.useState({});
|
||||
const [coin, setCoin] = React.useState('bitcoin');
|
||||
const [lastStatusQuery, setLastStatusQuery] = React.useState();
|
||||
const { goBack } = useHistory();
|
||||
|
||||
function formatCoinAmountString(amount) {
|
||||
return amount === 0 ? '---' : amount.toLocaleString(undefined, { minimumFractionDigits: 8 });
|
||||
}
|
||||
|
||||
function returnToMainAction() {
|
||||
setIsSwapping(false);
|
||||
setAction(ACTION_MAIN);
|
||||
setSwap(null);
|
||||
}
|
||||
|
||||
function removeCoinSwap(chargeCode) {
|
||||
openModal(MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS, {
|
||||
chargeCode: chargeCode,
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure 'receiveAddress' is populated
|
||||
React.useEffect(() => {
|
||||
if (!receiveAddress) {
|
||||
getNewAddress();
|
||||
} else {
|
||||
checkAddressIsMine(receiveAddress);
|
||||
}
|
||||
}, [receiveAddress, getNewAddress, checkAddressIsMine]);
|
||||
|
||||
// Get 'btc/rate' and calculate required BTC.
|
||||
React.useEffect(() => {
|
||||
if (isNaN(lbc) || lbc === 0) {
|
||||
setBtc(0);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsFetchingRate(true);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
Lbryio.call('btc', 'rate', { satoshi: BTC_SATOSHIS })
|
||||
.then((rate) => {
|
||||
setIsFetchingRate(false);
|
||||
setBtc((lbc * Math.round(BTC_SATOSHIS * rate)) / BTC_SATOSHIS);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsFetchingRate(false);
|
||||
setBtc(0);
|
||||
setNag({ msg: NAG_RATE_CALL_FAILED, type: 'error' });
|
||||
});
|
||||
}, DEBOUNCE_BTC_CHANGE_MS);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [lbc]);
|
||||
|
||||
// Resolve 'swap' with the latest info from 'coinSwaps'
|
||||
React.useEffect(() => {
|
||||
const swapInfo = swap && coinSwaps.find((x) => x.chargeCode === swap.chargeCode);
|
||||
if (!swapInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonSwap = JSON.stringify(swap);
|
||||
const jsonSwapInfo = JSON.stringify(swapInfo);
|
||||
if (jsonSwap !== jsonSwapInfo) {
|
||||
setSwap({ ...swapInfo });
|
||||
}
|
||||
|
||||
if (!swapInfo.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (swapInfo.status.status) {
|
||||
case BTC_API_STATUS_PENDING:
|
||||
setAction(ACTION_STATUS_PENDING);
|
||||
setNag({ msg: NAG_API_STATUS_PENDING, type: 'helpful' });
|
||||
break;
|
||||
case BTC_API_STATUS_CONFIRMING:
|
||||
setAction(ACTION_STATUS_CONFIRMING);
|
||||
setNag({ msg: NAG_API_STATUS_CONFIRMING, type: 'helpful' });
|
||||
break;
|
||||
case BTC_API_STATUS_PROCESSING:
|
||||
if (swapInfo.status.lbcTxid) {
|
||||
setAction(ACTION_STATUS_SUCCESS);
|
||||
setNag({ msg: NAG_API_STATUS_SUCCESS, type: 'helpful' });
|
||||
setIsSwapping(false);
|
||||
} else {
|
||||
setAction(ACTION_STATUS_PROCESSING);
|
||||
setNag({ msg: NAG_API_STATUS_PROCESSING, type: 'helpful' });
|
||||
}
|
||||
break;
|
||||
case BTC_API_STATUS_ERROR:
|
||||
setNag({ msg: NAG_API_STATUS_ERROR, type: 'error' });
|
||||
break;
|
||||
case INTERNAL_APIS_DOWN:
|
||||
setNag({ msg: NAG_SERVER_DOWN, type: 'error' });
|
||||
break;
|
||||
case BTC_API_STATUS_EXPIRED:
|
||||
setNag({ msg: NAG_EXPIRED, type: 'error' });
|
||||
if (action === ACTION_PAST_SWAPS) {
|
||||
setAction(ACTION_STATUS_PENDING);
|
||||
}
|
||||
break;
|
||||
case BTC_API_STATUS_UNRESOLVED:
|
||||
setNag({
|
||||
msg: __(
|
||||
'Received amount did not match order code %chargeCode%. Contact hello@lbry.com to resolve the payment.',
|
||||
{ chargeCode: swapInfo.chargeCode }
|
||||
),
|
||||
type: 'error',
|
||||
});
|
||||
if (action === ACTION_PAST_SWAPS) {
|
||||
setAction(ACTION_STATUS_PENDING);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setNag({ msg: swapInfo.status.status, type: 'error' });
|
||||
break;
|
||||
}
|
||||
}, [swap, coinSwaps]);
|
||||
|
||||
// Validate entered LBC
|
||||
React.useEffect(() => {
|
||||
let msg;
|
||||
if (lbc < LBC_MIN) {
|
||||
msg = __('The amount needs to be higher');
|
||||
} else if (lbc > LBC_MAX) {
|
||||
msg = __('The amount is too high');
|
||||
}
|
||||
setLbcError(msg);
|
||||
}, [lbc]);
|
||||
|
||||
// 'Refresh' button feedback
|
||||
React.useEffect(() => {
|
||||
let timer;
|
||||
if (isRefreshingStatus) {
|
||||
timer = setTimeout(() => {
|
||||
setIsRefreshingStatus(false);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isRefreshingStatus]);
|
||||
|
||||
function getCoinAddress(coin) {
|
||||
if (swap && swap.sendAddresses) {
|
||||
return swap.sendAddresses[coin];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getCoinSendAmountStr(coin) {
|
||||
if (swap && swap.sendAmounts && swap.sendAmounts[coin]) {
|
||||
return `${swap.sendAmounts[coin].amount} ${swap.sendAmounts[coin].currency}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function currencyToCoin(currency) {
|
||||
const MAP = {
|
||||
DAI: 'dai',
|
||||
USDC: 'usdc',
|
||||
BTC: 'bitcoin',
|
||||
ETH: 'ethereum',
|
||||
LTC: 'litecoin',
|
||||
BCH: 'bitcoincash',
|
||||
};
|
||||
return MAP[currency] || 'bitcoin';
|
||||
}
|
||||
|
||||
function getSentAmountStr(swapInfo) {
|
||||
if (swapInfo && swapInfo.status) {
|
||||
const currency = swapInfo.status.receiptCurrency;
|
||||
const coin = currencyToCoin(currency);
|
||||
return getCoinSendAmountStr(coin);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getCoinLabel(coin) {
|
||||
const COIN_LABEL = {
|
||||
dai: 'Dai',
|
||||
usdc: 'USD Coin',
|
||||
bitcoin: 'Bitcoin',
|
||||
ethereum: 'Ethereum',
|
||||
litecoin: 'Litecoin',
|
||||
bitcoincash: 'Bitcoin Cash',
|
||||
};
|
||||
|
||||
return COIN_LABEL[coin] || coin;
|
||||
}
|
||||
|
||||
function getLbcAmountStrForSwap(swap) {
|
||||
if (swap && swap.lbcAmount) {
|
||||
return formatCoinAmountString(swap.lbcAmount);
|
||||
}
|
||||
return '---';
|
||||
}
|
||||
|
||||
function handleStartSwap() {
|
||||
setIsSwapping(true);
|
||||
setSwap(null);
|
||||
setNag(null);
|
||||
|
||||
Lbryio.call('btc', 'swap', {
|
||||
lbc_satoshi_requested: parseInt(lbc * BTC_SATOSHIS + 0.5),
|
||||
btc_satoshi_provided: parseInt(btc * BTC_SATOSHIS + 0.5),
|
||||
pay_to_wallet_address: receiveAddress,
|
||||
})
|
||||
.then((response) => {
|
||||
const btcAmount = response.Charge.data.pricing['bitcoin'].amount;
|
||||
const rate = response.Exchange.rate;
|
||||
|
||||
const timeline = response.Charge.data.timeline;
|
||||
const lastTimeline = timeline[timeline.length - 1];
|
||||
|
||||
const newSwap = {
|
||||
chargeCode: response.Exchange.charge_code,
|
||||
coins: Object.keys(response.Charge.data.addresses),
|
||||
sendAddresses: response.Charge.data.addresses,
|
||||
sendAmounts: response.Charge.data.pricing,
|
||||
lbcAmount: (btcAmount * BTC_SATOSHIS) / rate,
|
||||
status: {
|
||||
status: lastTimeline.status,
|
||||
receiptCurrency: lastTimeline.payment.value.currency,
|
||||
receiptTxid: lastTimeline.payment.transaction_id,
|
||||
lbcTxid: response.Exchange.lbc_txid || '',
|
||||
},
|
||||
};
|
||||
|
||||
setSwap({ ...newSwap });
|
||||
addCoinSwap({ ...newSwap });
|
||||
})
|
||||
.catch((err) => {
|
||||
const translateError = (err) => {
|
||||
// TODO: https://github.com/lbryio/lbry.go/issues/87
|
||||
// Translate error codes instead of strings when it is available.
|
||||
if (err === 'users are currently limited to 4 transactions per month') {
|
||||
return __('Users are currently limited to 4 completed swaps per month or 5 pending swaps.');
|
||||
}
|
||||
return err;
|
||||
};
|
||||
setNag({ msg: err === INTERNAL_APIS_DOWN ? NAG_SWAP_CALL_FAILED : translateError(err.message), type: 'error' });
|
||||
returnToMainAction();
|
||||
});
|
||||
}
|
||||
|
||||
function handleViewPastSwaps() {
|
||||
setAction(ACTION_PAST_SWAPS);
|
||||
setNag(null);
|
||||
setIsRefreshingStatus(true);
|
||||
|
||||
const now = Date.now();
|
||||
if (!lastStatusQuery || now - lastStatusQuery > 30000) {
|
||||
// There is a '200/minute' limit in the commerce API. If the history is
|
||||
// long, or if the user goes trigger-happy, the limit could be reached
|
||||
// easily. Statuses don't change often, so just limit it to every 30s.
|
||||
setLastStatusQuery(now);
|
||||
coinSwaps.forEach((x) => {
|
||||
queryCoinSwapStatus(x.chargeCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getShortStatusStr(coinSwap: CoinSwapInfo) {
|
||||
const swapInfo = coinSwaps.find((x) => x.chargeCode === coinSwap.chargeCode);
|
||||
if (!swapInfo || !swapInfo.status) {
|
||||
return '---';
|
||||
}
|
||||
|
||||
let msg;
|
||||
switch (swapInfo.status.status) {
|
||||
case BTC_API_STATUS_PENDING:
|
||||
msg = __('Waiting');
|
||||
break;
|
||||
case BTC_API_STATUS_CONFIRMING:
|
||||
msg = __('Confirming');
|
||||
break;
|
||||
case BTC_API_STATUS_PROCESSING:
|
||||
if (swapInfo.status.lbcTxid) {
|
||||
msg = __('Credits sent');
|
||||
} else {
|
||||
msg = __('Sending Credits');
|
||||
}
|
||||
break;
|
||||
case BTC_API_STATUS_ERROR:
|
||||
msg = __('Failed');
|
||||
break;
|
||||
case BTC_API_STATUS_EXPIRED:
|
||||
msg = __('Expired');
|
||||
break;
|
||||
case BTC_API_STATUS_UNRESOLVED:
|
||||
msg = __('Unresolved');
|
||||
break;
|
||||
default:
|
||||
msg = swapInfo.status.status;
|
||||
// if (IS_DEV) throw new Error('Unhandled "status": ' + status.Status);
|
||||
break;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
function getViewTransactionElement(swap, isSend) {
|
||||
if (!swap || !swap.status) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const explorerUrl = (coin, txid) => {
|
||||
// It's unclear whether we can link to sites like blockchain.com.
|
||||
// Don't do it for now.
|
||||
return '';
|
||||
};
|
||||
|
||||
if (isSend) {
|
||||
const sendTxId = swap.status.receiptTxid;
|
||||
const url = explorerUrl(swap.status.receiptCurrency, sendTxId);
|
||||
return sendTxId ? (
|
||||
<>
|
||||
{url && <Button button="link" href={url} label={__('View transaction')} />}
|
||||
{!url && (
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Copy transaction ID')}
|
||||
title={sendTxId}
|
||||
onClick={() => {
|
||||
clipboard.writeText(sendTxId);
|
||||
doToast({
|
||||
message: __('Transaction ID copied.'),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null;
|
||||
} else {
|
||||
const lbcTxId = swap.status.lbcTxid;
|
||||
return lbcTxId ? (
|
||||
<Button button="link" href={`https://explorer.lbry.com/tx/${lbcTxId}`} label={__('View transaction')} />
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
function getCloseButton() {
|
||||
return (
|
||||
<>
|
||||
<Button autoFocus button="primary" label={__('Close')} onClick={() => goBack()} />
|
||||
<Icon
|
||||
className="icon--help"
|
||||
icon={ICONS.HELP}
|
||||
tooltip
|
||||
size={16}
|
||||
customTooltipText={__(
|
||||
'This page can be closed while the transactions are in progress.\nYou can view the status later from:\n • Wallet » Swap » View Past Swaps'
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getGap() {
|
||||
return <div className="confirm__value" />; // better way?
|
||||
}
|
||||
|
||||
function getActionElement() {
|
||||
switch (action) {
|
||||
case ACTION_MAIN:
|
||||
return actionMain;
|
||||
|
||||
case ACTION_STATUS_PENDING:
|
||||
return actionPending;
|
||||
|
||||
case ACTION_STATUS_CONFIRMING:
|
||||
return actionConfirmingSend;
|
||||
|
||||
case ACTION_STATUS_PROCESSING: // fall-through
|
||||
case ACTION_STATUS_SUCCESS:
|
||||
return actionProcessingAndSuccess;
|
||||
|
||||
case ACTION_PAST_SWAPS:
|
||||
return actionPastSwaps;
|
||||
|
||||
default:
|
||||
if (IS_DEV) throw new Error('Unhandled action: ' + action);
|
||||
return actionMain;
|
||||
}
|
||||
}
|
||||
|
||||
const actionMain = (
|
||||
<>
|
||||
<div className="section section--padded card--inline confirm__wrapper">
|
||||
<div className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
label={
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
lbc: <LbcSymbol size={22} />,
|
||||
}}
|
||||
>
|
||||
Enter desired %lbc%
|
||||
</I18nMessage>
|
||||
}
|
||||
type="number"
|
||||
name="lbc"
|
||||
className="form-field--price-amount--auto"
|
||||
affixClass="form-field--fix-no-height"
|
||||
max={LBC_MAX}
|
||||
min={LBC_MIN}
|
||||
step={1 / BTC_SATOSHIS}
|
||||
placeholder="12.34"
|
||||
value={lbc}
|
||||
error={lbcError}
|
||||
disabled={isSwapping}
|
||||
onChange={(event) => setLbc(parseFloat(event.target.value))}
|
||||
/>
|
||||
{getGap()}
|
||||
<div className="confirm__label">{__('Estimated BTC price')}</div>
|
||||
<div className="confirm__value">
|
||||
{formatCoinAmountString(btc)} {btc === 0 ? '' : 'BTC'}
|
||||
{isFetchingRate && <Spinner type="small" />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
autoFocus
|
||||
onClick={handleStartSwap}
|
||||
button="primary"
|
||||
disabled={isSwapping || isNaN(btc) || btc === 0 || lbc === 0 || lbcError}
|
||||
label={isSwapping ? __('Processing...') : __('Start Swap')}
|
||||
/>
|
||||
{!isSwapping && coinSwaps.length !== 0 && (
|
||||
<Button button="link" label={__('View Past Swaps')} onClick={handleViewPastSwaps} />
|
||||
)}
|
||||
{isSwapping && <Spinner type="small" />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const actionPending = (
|
||||
<>
|
||||
<div className="section section--padded card--inline confirm__wrapper">
|
||||
<div className="section">
|
||||
{swap && swap.coins && ENABLE_ALTERNATIVE_COINS && (
|
||||
<>
|
||||
<FormField
|
||||
type="select"
|
||||
name="select_coin"
|
||||
value={coin}
|
||||
label={__('Alternative coins')}
|
||||
onChange={(e) => setCoin(e.target.value)}
|
||||
>
|
||||
{swap.coins.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{getCoinLabel(x)}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
{getGap()}
|
||||
</>
|
||||
)}
|
||||
<div className="confirm__label">{__('Send')}</div>
|
||||
<CopyableText
|
||||
primaryButton
|
||||
copyable={getCoinSendAmountStr(coin)}
|
||||
snackMessage={__('Amount copied.')}
|
||||
onCopy={(inputElem) => {
|
||||
const inputStr = inputElem.value;
|
||||
const selectEndIndex = inputStr.lastIndexOf(' ');
|
||||
if (selectEndIndex > -1 && inputStr.substring(0, selectEndIndex).match(/[\d.]/)) {
|
||||
inputElem.setSelectionRange(0, selectEndIndex, 'forward');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="help">{__('Use the copy button to ensure the EXACT amount is sent!')}</div>
|
||||
{getGap()}
|
||||
<div className="confirm__label">{__('To')}</div>
|
||||
<CopyableText primaryButton copyable={getCoinAddress(coin)} snackMessage={__('Address copied.')} />
|
||||
<div className="confirm__value--subitem">
|
||||
<Button
|
||||
button="link"
|
||||
label={showQr ? __('Hide QR code') : __('Show QR code')}
|
||||
onClick={() => setShowQr(!showQr)}
|
||||
/>
|
||||
{showQr && getCoinAddress(coin) && <QRCode value={getCoinAddress(coin)} />}
|
||||
</div>
|
||||
{getGap()}
|
||||
<div className="confirm__label">{__('Receive')}</div>
|
||||
<div className="confirm__value">{<LbcSymbol postfix={getLbcAmountStrForSwap(swap)} size={22} />}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section__actions">{getCloseButton()}</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const actionConfirmingSend = (
|
||||
<>
|
||||
<div className="section section--padded card--inline confirm__wrapper">
|
||||
<div className="section">
|
||||
<div className="confirm__label">{__('Confirming')}</div>
|
||||
<div className="confirm__value confirm__value--no-gap">{getSentAmountStr(swap)}</div>
|
||||
<div className="confirm__value--subitem">{getViewTransactionElement(swap, true)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section__actions">{getCloseButton()}</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const actionProcessingAndSuccess = (
|
||||
<>
|
||||
<div className="section section--padded card--inline confirm__wrapper">
|
||||
<div className="section">
|
||||
<div className="confirm__label">{__('Sent')}</div>
|
||||
<div className="confirm__value confirm__value--no-gap">{getSentAmountStr(swap)}</div>
|
||||
<div className="confirm__value--subitem">{getViewTransactionElement(swap, true)}</div>
|
||||
{getGap()}
|
||||
<div className="confirm__label">{action === ACTION_STATUS_SUCCESS ? __('Received') : __('Receiving')}</div>
|
||||
<div className="confirm__value confirm__value--no-gap">
|
||||
{<LbcSymbol postfix={getLbcAmountStrForSwap(swap)} size={22} />}
|
||||
</div>
|
||||
{action === ACTION_STATUS_SUCCESS && (
|
||||
<div className="confirm__value--subitem">{getViewTransactionElement(swap, false)}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="section__actions">{getCloseButton()}</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const actionPastSwaps = (
|
||||
<>
|
||||
<div className="section section--padded card--inline confirm__wrapper">
|
||||
<div className="section">
|
||||
<div className="table__wrapper">
|
||||
<table className="table table--btc-swap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{__('Code')}</th>
|
||||
<th>{__('Status')}</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{coinSwaps.length === 0 && (
|
||||
<tr>
|
||||
<td>{'---'}</td>
|
||||
</tr>
|
||||
)}
|
||||
{coinSwaps.length !== 0 &&
|
||||
coinSwaps.map((x) => {
|
||||
return (
|
||||
<tr key={x.chargeCode}>
|
||||
<td>
|
||||
<Button
|
||||
button="link"
|
||||
className="button--hash-id"
|
||||
title={x.chargeCode}
|
||||
label={x.chargeCode}
|
||||
onClick={() => {
|
||||
setSwap({ ...x });
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>{isRefreshingStatus ? '...' : getShortStatusStr(x)}</td>
|
||||
<td>
|
||||
<Button
|
||||
button="link"
|
||||
icon={ICONS.REMOVE}
|
||||
title={__('Remove swap')}
|
||||
onClick={() => removeCoinSwap(x.chargeCode)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
autoFocus
|
||||
button="primary"
|
||||
label={__('Go Back')}
|
||||
onClick={() => {
|
||||
returnToMainAction();
|
||||
setNag(null);
|
||||
}}
|
||||
/>
|
||||
{coinSwaps.length !== 0 && !isRefreshingStatus && (
|
||||
<Button button="link" label={__('Refresh')} onClick={handleViewPastSwaps} />
|
||||
)}
|
||||
{isRefreshingStatus && <Spinner type="small" />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Redirect to={`/$/${PAGES.AUTH_SIGNIN}?redirect=${location.pathname}`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleStartSwap}>
|
||||
<Card
|
||||
title={<I18nMessage tokens={{ lbc: <LbcSymbol size={22} /> }}>Swap Crypto for %lbc%</I18nMessage>}
|
||||
subtitle={__(
|
||||
'Send crypto to the address provided and you will be sent an equivalent amount of Credits. You can pay with BCH, LTC, ETH, USDC or DAI after starting the swap.'
|
||||
)}
|
||||
actions={getActionElement()}
|
||||
nag={nag ? <Nag relative type={nag.type} message={__(nag.msg)} /> : null}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default WalletSwap;
|
|
@ -431,11 +431,6 @@ export const COMMENT_SUPER_CHAT_LIST_FAILED = 'COMMENT_SUPER_CHAT_LIST_FAILED';
|
|||
// Blocked channels
|
||||
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
||||
|
||||
// Coin swap
|
||||
export const ADD_COIN_SWAP = 'ADD_COIN_SWAP';
|
||||
export const REMOVE_COIN_SWAP = 'REMOVE_COIN_SWAP';
|
||||
export const COIN_SWAP_STATUS_RECEIVED = 'COIN_SWAP_STATUS_RECEIVED';
|
||||
|
||||
// Tags
|
||||
export const TOGGLE_TAG_FOLLOW = 'TOGGLE_TAG_FOLLOW';
|
||||
export const TAG_ADD = 'TAG_ADD';
|
||||
|
|
|
@ -40,7 +40,6 @@ export const SYNC_ENABLE = 'SYNC_ENABLE';
|
|||
export const IMAGE_UPLOAD = 'image_upload';
|
||||
export const MOBILE_SEARCH = 'mobile_search';
|
||||
export const VIEW_IMAGE = 'view_image';
|
||||
export const CONFIRM_REMOVE_BTC_SWAP_ADDRESS = 'confirm_remove_btc_swap_address';
|
||||
export const BLOCK_CHANNEL = 'block_channel';
|
||||
export const COLLECTION_ADD = 'collection_add';
|
||||
export const COLLECTION_DELETE = 'collection_delete';
|
||||
|
|
|
@ -51,7 +51,6 @@ export const PAGE_TITLE = {
|
|||
[PAGES.SETTINGS_STRIPE_CARD]: 'Payment Methods',
|
||||
[PAGES.SETTINGS_UPDATE_PWD]: 'Update password',
|
||||
[PAGES.SETTINGS_OWN_COMMENTS]: 'Your comments',
|
||||
[PAGES.SWAP]: 'Swap Credits',
|
||||
[PAGES.TAGS_FOLLOWING]: 'Tags',
|
||||
[PAGES.TAGS_FOLLOWING_MANAGE]: 'Manage tags',
|
||||
[PAGES.UPLOAD]: 'Upload',
|
||||
|
|
|
@ -70,7 +70,6 @@ exports.CODE_2257 = '2257';
|
|||
exports.BUY = 'buy';
|
||||
exports.RECEIVE = 'receive';
|
||||
exports.SEND = 'send';
|
||||
exports.SWAP = 'swap';
|
||||
exports.CHANNEL_NEW = 'channel/new';
|
||||
exports.NOTIFICATIONS = 'notifications';
|
||||
exports.YOUTUBE_SYNC = 'youtube';
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalRemoveBtcSwapAddress from './view';
|
||||
import { doRemoveCoinSwap } from 'redux/actions/coinSwap';
|
||||
|
||||
const select = (state, props) => ({});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
removeCoinSwap: (chargeCode) => dispatch(doRemoveCoinSwap(chargeCode)),
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ModalRemoveBtcSwapAddress);
|
|
@ -1,43 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
import Card from 'component/common/card';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
|
||||
type Props = {
|
||||
chargeCode: string,
|
||||
removeCoinSwap: (string) => void,
|
||||
closeModal: () => void,
|
||||
};
|
||||
|
||||
function ModalRemoveBtcSwapAddress(props: Props) {
|
||||
const { chargeCode, removeCoinSwap, closeModal } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen contentLabel={__('Confirm Swap Removal')} type="card" onAborted={closeModal}>
|
||||
<Card
|
||||
title={__('Remove Swap')}
|
||||
subtitle={<I18nMessage tokens={{ address: <em>{`${chargeCode}`}</em> }}>Remove %address%?</I18nMessage>}
|
||||
body={<p className="help--warning">{__('This process cannot be reversed.')}</p>}
|
||||
actions={
|
||||
<>
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('OK')}
|
||||
onClick={() => {
|
||||
removeCoinSwap(chargeCode);
|
||||
closeModal();
|
||||
}}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalRemoveBtcSwapAddress;
|
|
@ -41,8 +41,6 @@ import ModalPasswordUnsave from 'modal/modalPasswordUnsave';
|
|||
import ModalPublish from 'modal/modalPublish';
|
||||
import ModalPublishPreview from 'modal/modalPublishPreview';
|
||||
|
||||
import ModalRemoveBtcSwapAddress from 'modal/modalRemoveBtcSwapAddress';
|
||||
|
||||
import ModalRemoveCard from 'modal/modalRemoveCard';
|
||||
import ModalRemoveComment from 'modal/modalRemoveComment';
|
||||
|
||||
|
@ -137,8 +135,6 @@ function getModal(id) {
|
|||
return ModalViewImage;
|
||||
case MODALS.MASS_TIP_UNLOCK:
|
||||
return ModalMassTipsUnlock;
|
||||
case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS:
|
||||
return ModalRemoveBtcSwapAddress;
|
||||
case MODALS.BLOCK_CHANNEL:
|
||||
return ModalBlockChannel;
|
||||
case MODALS.COLLECTION_ADD:
|
||||
|
|
|
@ -313,39 +313,6 @@ export default function SettingsCreatorPage(props: Props) {
|
|||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={
|
||||
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for hyperchats</I18nMessage>
|
||||
}
|
||||
subtitle={
|
||||
<>
|
||||
{__(HELP.MIN_SUPER)}
|
||||
{minTip !== 0 && (
|
||||
<p className="help--inline">
|
||||
<em>{__(HELP.MIN_SUPER_OFF)}</em>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormField
|
||||
name="min_tip_amount_super_chat"
|
||||
className="form-field--price-amount"
|
||||
min={0}
|
||||
step="any"
|
||||
type="number"
|
||||
placeholder="1"
|
||||
value={minSuper}
|
||||
disabled={minTip !== 0}
|
||||
onChange={(e) => {
|
||||
const newMinSuper = parseFloat(e.target.value);
|
||||
setMinSuper(newMinSuper);
|
||||
pushMinSuperDebounced(newMinSuper, activeChannelClaim);
|
||||
}}
|
||||
onBlur={() => setLastUpdated(Date.now())}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow title={__('Moderators')} subtitle={__(HELP.MODERATORS)} multirow>
|
||||
<SearchChannelField
|
||||
label={__('Moderators')}
|
||||
|
|
|
@ -16,7 +16,6 @@ import rewardsReducer from 'redux/reducers/rewards';
|
|||
import userReducer from 'redux/reducers/user';
|
||||
import commentsReducer from 'redux/reducers/comments';
|
||||
import blockedReducer from 'redux/reducers/blocked';
|
||||
import coinSwapReducer from 'redux/reducers/coinSwap';
|
||||
import searchReducer from 'redux/reducers/search';
|
||||
import reactionsReducer from 'redux/reducers/reactions';
|
||||
import syncReducer from 'redux/reducers/sync';
|
||||
|
@ -44,7 +43,6 @@ export default (history) =>
|
|||
subscriptions: subscriptionsReducer,
|
||||
tags: tagsReducer,
|
||||
blocked: blockedReducer,
|
||||
coinSwap: coinSwapReducer,
|
||||
user: userReducer,
|
||||
wallet: walletReducer,
|
||||
sync: syncReducer,
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { selectPrefsReady } from 'redux/selectors/sync';
|
||||
import { doAlertWaitingForSync } from 'redux/actions/app';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
|
||||
export const doAddCoinSwap = (coinSwapInfo: CoinSwapInfo) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const ready = selectPrefsReady(state);
|
||||
|
||||
if (!ready) {
|
||||
return dispatch(doAlertWaitingForSync());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.ADD_COIN_SWAP,
|
||||
data: coinSwapInfo,
|
||||
});
|
||||
};
|
||||
|
||||
export const doRemoveCoinSwap = (chargeCode: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const ready = selectPrefsReady(state);
|
||||
|
||||
if (!ready) {
|
||||
return dispatch(doAlertWaitingForSync());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.REMOVE_COIN_SWAP,
|
||||
data: {
|
||||
chargeCode,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const doQueryCoinSwapStatus = (chargeCode: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
Lbryio.call('btc', 'status', { charge_code: chargeCode }).then((response) => {
|
||||
dispatch({
|
||||
type: ACTIONS.COIN_SWAP_STATUS_RECEIVED,
|
||||
data: response,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -98,24 +98,22 @@ export function doSetSync(oldHash: string, newHash: string, data: any) {
|
|||
};
|
||||
}
|
||||
|
||||
export const doGetSyncDesktop = (cb?: (any, any) => void, password?: string) => (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const state = getState();
|
||||
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
|
||||
const getSyncPending = selectGetSyncIsPending(state);
|
||||
const setSyncPending = selectSetSyncIsPending(state);
|
||||
const syncLocked = selectSyncIsLocked(state);
|
||||
export const doGetSyncDesktop =
|
||||
(cb?: (any, any) => void, password?: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
|
||||
const getSyncPending = selectGetSyncIsPending(state);
|
||||
const setSyncPending = selectSetSyncIsPending(state);
|
||||
const syncLocked = selectSyncIsLocked(state);
|
||||
|
||||
return getSavedPassword().then((savedPassword) => {
|
||||
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
|
||||
return getSavedPassword().then((savedPassword) => {
|
||||
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
|
||||
|
||||
if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) {
|
||||
return dispatch(doGetSync(passwordArgument, cb));
|
||||
}
|
||||
});
|
||||
};
|
||||
if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) {
|
||||
return dispatch(doGetSync(passwordArgument, cb));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export function doSyncLoop(noInterval?: boolean) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
|
@ -385,7 +383,6 @@ type SharedData = {
|
|||
following?: Array<{ uri: string, notificationsDisabled: boolean }>,
|
||||
tags?: Array<string>,
|
||||
blocked?: Array<string>,
|
||||
coin_swap_codes?: Array<string>,
|
||||
settings?: any,
|
||||
app_welcome_version?: number,
|
||||
sharing_3P?: boolean,
|
||||
|
@ -403,7 +400,6 @@ function extractUserState(rawObj: SharedData) {
|
|||
following,
|
||||
tags,
|
||||
blocked,
|
||||
coin_swap_codes,
|
||||
settings,
|
||||
app_welcome_version,
|
||||
sharing_3P,
|
||||
|
@ -418,7 +414,6 @@ function extractUserState(rawObj: SharedData) {
|
|||
...(following ? { following } : {}),
|
||||
...(tags ? { tags } : {}),
|
||||
...(blocked ? { blocked } : {}),
|
||||
...(coin_swap_codes ? { coin_swap_codes } : {}),
|
||||
...(settings ? { settings } : {}),
|
||||
...(app_welcome_version ? { app_welcome_version } : {}),
|
||||
...(sharing_3P ? { sharing_3P } : {}),
|
||||
|
@ -445,7 +440,6 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
|||
following,
|
||||
tags,
|
||||
blocked,
|
||||
coin_swap_codes,
|
||||
settings,
|
||||
app_welcome_version,
|
||||
sharing_3P,
|
||||
|
@ -470,7 +464,6 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
|||
following,
|
||||
tags,
|
||||
blocked,
|
||||
coinSwapCodes: coin_swap_codes,
|
||||
walletPrefSettings: settings,
|
||||
mergedClientSettings,
|
||||
welcomeVersion: app_welcome_version,
|
||||
|
|
|
@ -82,12 +82,6 @@ export const doNotificationSocketConnect = (enableNotifications) => (dispatch) =
|
|||
dispatch(doNotificationList());
|
||||
}
|
||||
break;
|
||||
case 'swap-status':
|
||||
dispatch({
|
||||
type: ACTIONS.COIN_SWAP_STATUS_RECEIVED,
|
||||
data: data.data,
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const SWAP_HISTORY_LENGTH_LIMIT = 10;
|
||||
|
||||
function getBottomEntries(array, count) {
|
||||
const curCount = array.length;
|
||||
if (curCount < count) {
|
||||
return array;
|
||||
} else {
|
||||
return array.slice(curCount - count);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultState: CoinSwapState = {
|
||||
coinSwaps: [],
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[ACTIONS.ADD_COIN_SWAP]: (state: CoinSwapState, action: CoinSwapAddAction): CoinSwapState => {
|
||||
const { coinSwaps } = state;
|
||||
const { chargeCode } = action.data;
|
||||
|
||||
const newCoinSwaps = coinSwaps.slice();
|
||||
if (!newCoinSwaps.find((x) => x.chargeCode === chargeCode)) {
|
||||
newCoinSwaps.push({ ...action.data });
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
coinSwaps: newCoinSwaps,
|
||||
};
|
||||
},
|
||||
[ACTIONS.REMOVE_COIN_SWAP]: (state: CoinSwapState, action: CoinSwapRemoveAction): CoinSwapState => {
|
||||
const { coinSwaps } = state;
|
||||
const { chargeCode } = action.data;
|
||||
let newCoinSwaps = coinSwaps.slice();
|
||||
newCoinSwaps = newCoinSwaps.filter((x) => x.chargeCode !== chargeCode);
|
||||
return {
|
||||
...state,
|
||||
coinSwaps: newCoinSwaps,
|
||||
};
|
||||
},
|
||||
[ACTIONS.COIN_SWAP_STATUS_RECEIVED]: (state: CoinSwapState, action: any) => {
|
||||
const { coinSwaps } = state;
|
||||
const newCoinSwaps = coinSwaps.slice();
|
||||
|
||||
let exchange;
|
||||
let charge;
|
||||
|
||||
if (action.data.event_data) {
|
||||
// Source: Websocket
|
||||
exchange = { lbc_txid: action.data.lbc_txid };
|
||||
charge = action.data.event_data;
|
||||
} else {
|
||||
// Source: btc/status
|
||||
exchange = action.data.Exchange;
|
||||
charge = action.data.Charge.data;
|
||||
}
|
||||
|
||||
const calculateLbcAmount = (pricing, exchange, fallback) => {
|
||||
if (!exchange || !exchange.rate) {
|
||||
return fallback || 0;
|
||||
}
|
||||
|
||||
const btcAmount = pricing['bitcoin'].amount;
|
||||
const SATOSHIS = 100000000;
|
||||
return (btcAmount * SATOSHIS) / exchange.rate;
|
||||
};
|
||||
|
||||
const timeline = charge.timeline;
|
||||
const lastTimeline = timeline[timeline.length - 1];
|
||||
|
||||
const index = newCoinSwaps.findIndex((x) => x.chargeCode === charge.code);
|
||||
if (index > -1) {
|
||||
newCoinSwaps[index] = {
|
||||
chargeCode: charge.code,
|
||||
coins: Object.keys(charge.addresses),
|
||||
sendAddresses: charge.addresses,
|
||||
sendAmounts: charge.pricing,
|
||||
lbcAmount: calculateLbcAmount(charge.pricing, exchange, newCoinSwaps[index].lbcAmount),
|
||||
status: {
|
||||
status: lastTimeline.status,
|
||||
receiptCurrency: lastTimeline.payment.value.currency,
|
||||
receiptTxid: lastTimeline.payment.transaction_id,
|
||||
lbcTxid: exchange.lbc_txid || '',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// If a pending swap is removed, the websocket will return an update
|
||||
// when it expires, for example, causing the entry to re-appear. This
|
||||
// might be a good thing (e.g. to get back accidental removals), but it
|
||||
// actually causes synchronization confusion across multiple instances.
|
||||
const IGNORED_DELETED_SWAPS = true;
|
||||
|
||||
if (!IGNORED_DELETED_SWAPS) {
|
||||
newCoinSwaps.push({
|
||||
chargeCode: charge.code,
|
||||
coins: Object.keys(charge.addresses),
|
||||
sendAddresses: charge.addresses,
|
||||
sendAmounts: charge.pricing,
|
||||
lbcAmount: calculateLbcAmount(charge.pricing, exchange, 0),
|
||||
status: {
|
||||
status: lastTimeline.status,
|
||||
receiptCurrency: lastTimeline.payment.value.currency,
|
||||
receiptTxid: lastTimeline.payment.transaction_id,
|
||||
lbcTxid: exchange.lbc_txid || '',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
coinSwaps: newCoinSwaps,
|
||||
};
|
||||
},
|
||||
[ACTIONS.SYNC_STATE_POPULATE]: (state: CoinSwapState, action: { data: { coinSwapCodes: ?Array<string> } }) => {
|
||||
const { coinSwapCodes } = action.data;
|
||||
const newCoinSwaps = [];
|
||||
|
||||
if (coinSwapCodes) {
|
||||
coinSwapCodes.forEach((chargeCode) => {
|
||||
if (chargeCode && typeof chargeCode === 'string') {
|
||||
const existingSwap = state.coinSwaps.find((x) => x.chargeCode === chargeCode);
|
||||
if (existingSwap) {
|
||||
newCoinSwaps.push({ ...existingSwap });
|
||||
} else {
|
||||
newCoinSwaps.push({
|
||||
// Just restore the 'chargeCode', and query the other data
|
||||
// via 'btc/status' later.
|
||||
chargeCode: chargeCode,
|
||||
coins: [],
|
||||
sendAddresses: {},
|
||||
sendAmounts: {},
|
||||
lbcAmount: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
coinSwaps: getBottomEntries(newCoinSwaps, SWAP_HISTORY_LENGTH_LIMIT),
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
|
@ -1,8 +0,0 @@
|
|||
// @flow
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const selectState = (state) => state.coinSwap || {};
|
||||
|
||||
export const selectCoinSwaps = createSelector(selectState, (state: CoinSwapState) => {
|
||||
return state.coinSwaps;
|
||||
});
|
26
ui/store.js
26
ui/store.js
|
@ -25,12 +25,14 @@ function isNotFunction(object) {
|
|||
}
|
||||
|
||||
function createBulkThunkMiddleware() {
|
||||
return ({ dispatch, getState }) => (next) => (action) => {
|
||||
if (action.type === 'BATCH_ACTIONS') {
|
||||
action.actions.filter(isFunction).map((actionFn) => actionFn(dispatch, getState));
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
return ({ dispatch, getState }) =>
|
||||
(next) =>
|
||||
(action) => {
|
||||
if (action.type === 'BATCH_ACTIONS') {
|
||||
action.actions.filter(isFunction).map((actionFn) => actionFn(dispatch, getState));
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
}
|
||||
|
||||
function enableBatching(reducer) {
|
||||
|
@ -68,7 +70,6 @@ const searchFilter = createFilter('search', ['options']);
|
|||
const tagsFilter = createFilter('tags', ['followedTags']);
|
||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||
const blockedFilter = createFilter('blocked', ['blockedChannels']);
|
||||
const coinSwapsFilter = createFilter('coinSwap', ['coinSwaps']);
|
||||
const settingsFilter = createBlacklistFilter('settings', ['loadedLanguages', 'language']);
|
||||
const collectionsFilter = createFilter('collections', ['builtin', 'saved', 'unpublished', 'edited', 'pending']);
|
||||
const whiteListedReducers = [
|
||||
|
@ -81,7 +82,6 @@ const whiteListedReducers = [
|
|||
'app',
|
||||
'search',
|
||||
'blocked',
|
||||
'coinSwap',
|
||||
'settings',
|
||||
'subscriptions',
|
||||
'collections',
|
||||
|
@ -92,7 +92,6 @@ const transforms = [
|
|||
fileInfoFilter,
|
||||
walletFilter,
|
||||
blockedFilter,
|
||||
coinSwapsFilter,
|
||||
tagsFilter,
|
||||
appFilter,
|
||||
searchFilter,
|
||||
|
@ -129,8 +128,6 @@ const triggerSharedStateActions = [
|
|||
ACTIONS.CHANNEL_SUBSCRIBE,
|
||||
ACTIONS.CHANNEL_UNSUBSCRIBE,
|
||||
ACTIONS.TOGGLE_BLOCK_CHANNEL,
|
||||
ACTIONS.ADD_COIN_SWAP,
|
||||
ACTIONS.REMOVE_COIN_SWAP,
|
||||
ACTIONS.TOGGLE_TAG_FOLLOW,
|
||||
ACTIONS.CREATE_CHANNEL_COMPLETED,
|
||||
ACTIONS.SYNC_CLIENT_SETTINGS,
|
||||
|
@ -168,13 +165,6 @@ const sharedStateFilters = {
|
|||
property: 'following',
|
||||
},
|
||||
blocked: { source: 'blocked', property: 'blockedChannels' },
|
||||
coin_swap_codes: {
|
||||
source: 'coinSwap',
|
||||
property: 'coinSwaps',
|
||||
transform: (coinSwaps) => {
|
||||
return coinSwaps.map((coinSwapInfo) => coinSwapInfo.chargeCode);
|
||||
},
|
||||
},
|
||||
settings: { source: 'settings', property: 'sharedPreferences' },
|
||||
app_welcome_version: { source: 'app', property: 'welcomeVersion' },
|
||||
sharing_3P: { source: 'app', property: 'allowAnalytics' },
|
||||
|
|
Loading…
Reference in a new issue