Browse Source

btc spv

v0.25
pbca26 7 years ago
parent
commit
ef40c5e1a8
  1. 1
      react/package.json
  2. 38
      react/src/actions/actions/electrum.js
  3. 4
      react/src/components/addcoin/addcoinOptionsCrypto.js
  4. 7
      react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.scss
  5. 171
      react/src/components/dashboard/sendCoin/sendCoin.js
  6. 42
      react/src/components/dashboard/sendCoin/sendCoin.render.js
  7. 35
      react/src/components/dashboard/sendCoin/sendCoin.scss
  8. 1
      react/src/components/dashboard/walletsNav/walletsNav.render.js
  9. 46
      react/src/components/overrides.scss
  10. 1
      react/src/styles/index.scss

1
react/package.json

@ -38,6 +38,7 @@
"express": "^4.14.0", "express": "^4.14.0",
"file-loader": "^0.10.0", "file-loader": "^0.10.0",
"qrcode.react": "^0.7.1", "qrcode.react": "^0.7.1",
"rc-slider": "8.5.0",
"react": "^15.3.1", "react": "^15.3.1",
"react-dom": "^15.3.1", "react-dom": "^15.3.1",
"react-hot-loader": "^1.3.0", "react-hot-loader": "^1.3.0",

38
react/src/actions/actions/electrum.js

@ -11,6 +11,32 @@ import {
} from '../actionCreators'; } from '../actionCreators';
import Store from '../../store'; import Store from '../../store';
// src: atomicexplorer
export function shepherdGetRemoteBTCFees() {
return new Promise((resolve, reject) => {
fetch(`http://atomicexplorer.com/api/btc/fees`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.catch((error) => {
console.log(error);
Store.dispatch(
triggerToaster(
'shepherdGetRemoteBTCFees',
'Error',
'error'
)
);
})
.then(response => response.json())
.then(json => {
resolve(json);
});
});
}
export function shepherdElectrumSetServer(coin, address, port) { export function shepherdElectrumSetServer(coin, address, port) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/coins/server/set?address=${address}&port=${port}&coin=${coin}&token=${Config.token}`, { fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/coins/server/set?address=${address}&port=${port}&coin=${coin}&token=${Config.token}`, {
@ -198,11 +224,11 @@ export function shepherdElectrumCoinsState(json) {
} }
// value in sats // value in sats
export function shepherdElectrumSend(coin, value, sendToAddress, changeAddress) { export function shepherdElectrumSend(coin, value, sendToAddress, changeAddress, btcFee) {
value = Math.floor(value); value = Math.floor(value);
return dispatch => { return dispatch => {
return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=true&verify=true&token=${Config.token}`, { return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}${btcFee ? '&btcfee=' + btcFee : ''}&gui=true&push=true&verify=true&token=${Config.token}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -225,11 +251,11 @@ export function shepherdElectrumSend(coin, value, sendToAddress, changeAddress)
} }
} }
export function shepherdElectrumSendPromise(coin, value, sendToAddress, changeAddress) { export function shepherdElectrumSendPromise(coin, value, sendToAddress, changeAddress, btcFee) {
value = Math.floor(value); value = Math.floor(value);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=true&verify=true&token=${Config.token}`, { return fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}${btcFee ? '&btcfee=' + btcFee : ''}&gui=true&push=true&verify=true&token=${Config.token}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -252,11 +278,11 @@ export function shepherdElectrumSendPromise(coin, value, sendToAddress, changeAd
}); });
} }
export function shepherdElectrumSendPreflight(coin, value, sendToAddress, changeAddress) { export function shepherdElectrumSendPreflight(coin, value, sendToAddress, changeAddress, btcFee) {
value = Math.floor(value); value = Math.floor(value);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}&gui=true&push=false&verify=true&token=${Config.token}`, { fetch(`http://127.0.0.1:${Config.agamaPort}/shepherd/electrum/createrawtx?coin=${coin}&address=${sendToAddress}&value=${value}&change=${changeAddress}${btcFee ? '&btcfee=' + btcFee : ''}&gui=true&push=false&verify=true&token=${Config.token}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

4
react/src/components/addcoin/addcoinOptionsCrypto.js

@ -24,11 +24,11 @@ const addCoinOptionsCrypto = () => {
label: 'BitcoinCash (BCH)', label: 'BitcoinCash (BCH)',
icon: 'BCH', icon: 'BCH',
value: `BCH|spv`, value: `BCH|spv`,
},/* { }, {
label: 'Bitcoin (BTC)', label: 'Bitcoin (BTC)',
icon: 'BTC', icon: 'BTC',
value: `BTC|spv`, value: `BTC|spv`,
}, */{ }, {
label: 'Crown (CRW)', label: 'Crown (CRW)',
icon: 'CRW', icon: 'CRW',
value: `CRW|spv`, value: `CRW|spv`,

7
react/src/components/dashboard/notaryElectionsModal/notaryElectionsModal.scss

@ -168,3 +168,10 @@
} }
} }
} }
.page-login .notary-elections-modal {
input,
textarea {
color: #5f5d5d;
}
}

171
react/src/components/dashboard/sendCoin/sendCoin.js

@ -10,6 +10,7 @@ import {
clearLastSendToResponseState, clearLastSendToResponseState,
shepherdElectrumSend, shepherdElectrumSend,
shepherdElectrumSendPreflight, shepherdElectrumSendPreflight,
shepherdGetRemoteBTCFees,
copyString, copyString,
} from '../../../actions/actionCreators'; } from '../../../actions/actionCreators';
import Store from '../../../store'; import Store from '../../../store';
@ -21,11 +22,20 @@ import {
} from './sendCoin.render'; } from './sendCoin.render';
import { isPositiveNumber } from '../../../util/number'; import { isPositiveNumber } from '../../../util/number';
import mainWindow from '../../../util/mainWindow'; import mainWindow from '../../../util/mainWindow';
import Slider, { Range } from 'rc-slider';
import ReactTooltip from 'react-tooltip';
// TODO: - add links to explorers // TODO: - add links to explorers
// - render z address trim // - render z address trim
// - handle click outside // - handle click outside
const _feeLookup = [
'fastestFee',
'halfHourFee',
'hourFee',
'advanced'
];
class SendCoin extends React.Component { class SendCoin extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -44,7 +54,14 @@ class SendCoin extends React.Component {
coin: null, coin: null,
spvVerificationWarning: false, spvVerificationWarning: false,
spvPreflightSendInProgress: false, spvPreflightSendInProgress: false,
btcFees: {},
btcFeesType: 'halfHourFee',
btcFeesAdvancedStep: 9,
btcFeesSize: 0,
btcFeesTimeBasedStep: 1,
btcPreflightRes: null,
}; };
this.defaultState = JSON.parse(JSON.stringify(this.state));
this.updateInput = this.updateInput.bind(this); this.updateInput = this.updateInput.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.openDropMenu = this.openDropMenu.bind(this); this.openDropMenu = this.openDropMenu.bind(this);
@ -58,13 +75,17 @@ class SendCoin extends React.Component {
this.isFullySynced = this.isFullySynced.bind(this); this.isFullySynced = this.isFullySynced.bind(this);
this.setSendAmountAll = this.setSendAmountAll.bind(this); this.setSendAmountAll = this.setSendAmountAll.bind(this);
this.setSendToSelf = this.setSendToSelf.bind(this); this.setSendToSelf = this.setSendToSelf.bind(this);
this.fetchBTCFees = this.fetchBTCFees.bind(this);
this.onSliderChange = this.onSliderChange.bind(this);
this.onSliderChangeTime = this.onSliderChangeTime.bind(this);
} }
setSendAmountAll() { setSendAmountAll() {
const _amount = this.state.amount; const _amount = this.state.amount;
const _amountSats = this.state.amount * 100000000; const _amountSats = this.state.amount * 100000000;
const _balanceSats = this.props.ActiveCoin.balance.balanceSats; const _balanceSats = this.props.ActiveCoin.balance.balanceSats;
const _fees = mainWindow.spvFees; let _fees = mainWindow.spvFees;
_fees.BTC = 0;
this.setState({ this.setState({
amount: Number((0.00000001 * (_balanceSats - _fees[this.props.ActiveCoin.coin])).toFixed(8)), amount: Number((0.00000001 * (_balanceSats - _fees[this.props.ActiveCoin.coin])).toFixed(8)),
@ -136,6 +157,11 @@ class SendCoin extends React.Component {
Store.dispatch(clearLastSendToResponseState()); Store.dispatch(clearLastSendToResponseState());
} }
this.checkZAddressCount(props); this.checkZAddressCount(props);
if (this.props.ActiveCoin.activeSection !== props.ActiveCoin.activeSection &&
this.props.ActiveCoin.activeSection !== 'send') {
this.fetchBTCFees();
}
} }
setRecieverFromScan(receiver) { setRecieverFromScan(receiver) {
@ -400,8 +426,29 @@ class SendCoin extends React.Component {
}); });
} }
fetchBTCFees() {
if (this.props.ActiveCoin.mode === 'spv' &&
this.props.ActiveCoin.coin === 'BTC') {
shepherdGetRemoteBTCFees()
.then((res) => {
if (res.msg === 'success') {
// TODO: check, approx fiat value
this.setState({
btcFees: res.result,
btcFeesSize: this.state.btcFeesType === 'advanced' ? res.result.electrum[this.state.btcFeesAdvancedStep] : res.result.recommended[_feeLookup[this.state.btcFeesTimeBasedStep]],
});
} else {
// TODO: fallback to local electrum
}
console.warn('btcfees', res);
});
}
}
changeSendCoinStep(step, back) { changeSendCoinStep(step, back) {
if (step === 0) { if (step === 0) {
this.fetchBTCFees();
if (back) { if (back) {
this.setState({ this.setState({
currentStep: 0, currentStep: 0,
@ -411,21 +458,7 @@ class SendCoin extends React.Component {
} else { } else {
Store.dispatch(clearLastSendToResponseState()); Store.dispatch(clearLastSendToResponseState());
this.setState({ this.setState(this.defaultState);
currentStep: 0,
addressType: null,
sendFrom: null,
sendFromAmount: 0,
sendTo: '',
sendToOA: null,
amount: 0,
fee: 0,
addressSelectorOpen: false,
renderAddressDropdown: true,
subtractFee: false,
spvVerificationWarning: false,
spvPreflightSendInProgress: false,
});
} }
} }
@ -444,7 +477,8 @@ class SendCoin extends React.Component {
this.props.ActiveCoin.coin, this.props.ActiveCoin.coin,
this.state.amount * 100000000, this.state.amount * 100000000,
this.state.sendTo, this.state.sendTo,
this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub,
this.props.ActiveCoin.coin === 'BTC' ? this.state.btcFeesSize : null
) )
.then((sendPreflight) => { .then((sendPreflight) => {
if (sendPreflight && if (sendPreflight &&
@ -452,6 +486,7 @@ class SendCoin extends React.Component {
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
spvVerificationWarning: !sendPreflight.result.utxoVerified, spvVerificationWarning: !sendPreflight.result.utxoVerified,
spvPreflightSendInProgress: false, spvPreflightSendInProgress: false,
btcPreflightRes: this.props.ActiveCoin.coin === 'BTC' ? { fee: sendPreflight.result.fee, value: sendPreflight.result.value, change: sendPreflight.result.change } : null,
})); }));
} else { } else {
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
@ -502,7 +537,8 @@ class SendCoin extends React.Component {
this.props.ActiveCoin.coin, this.props.ActiveCoin.coin,
this.state.amount * 100000000, this.state.amount * 100000000,
this.state.sendTo, this.state.sendTo,
this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub this.props.Dashboard.electrumCoins[this.props.ActiveCoin.coin].pub,
this.props.ActiveCoin.coin === 'BTC' ? this.state.btcFeesSize : null
) )
); );
} }
@ -517,7 +553,8 @@ class SendCoin extends React.Component {
const _amount = this.state.amount; const _amount = this.state.amount;
const _amountSats = this.state.amount * 100000000; const _amountSats = this.state.amount * 100000000;
const _balanceSats = this.props.ActiveCoin.balance.balanceSats; const _balanceSats = this.props.ActiveCoin.balance.balanceSats;
const _fees = mainWindow.spvFees; let _fees = mainWindow.spvFees;
_fees.BTC = 0;
if (Number(_amountSats) + _fees[this.props.ActiveCoin.coin] > _balanceSats) { if (Number(_amountSats) + _fees[this.props.ActiveCoin.coin] > _balanceSats) {
Store.dispatch( Store.dispatch(
@ -638,6 +675,102 @@ class SendCoin extends React.Component {
} }
} }
onSliderChange(value) {
console.warn(value);
console.warn(`btc fee /byte ${this.state.btcFees.electrum[value]}`);
this.setState({
btcFeesSize: this.state.btcFees.electrum[value],
btcFeesAdvancedStep: value,
});
}
onSliderChangeTime(value) {
console.warn(value);
console.warn(`btc fee /byte ${_feeLookup[value]}`);
this.setState({
btcFeesSize: this.state.btcFees.recommended[_feeLookup[value]],
btcFeesTimeBasedStep: value,
btcFeesType: _feeLookup[value] === 'advanced' ? 'advanced' : null,
});
}
renderBTCFees() {
if (this.props.ActiveCoin.mode === 'spv' &&
this.props.ActiveCoin.coin === 'BTC' &&
!this.state.btcFees.lastUpdated) {
return (<div className="col-lg-6 form-group form-material">Fetching BTC fees...</div>);
} else if (this.props.ActiveCoin.mode === 'spv' && this.props.ActiveCoin.coin === 'BTC' && this.state.btcFees.lastUpdated) {
const _min = 0;
const _max = this.state.btcFees.electrum.length - 1;
const _confTime = [
'within less than 30 min',
'within 30 min',
'within 60 min',
];
const _minTimeBased = 0;
const _maxTimeBased = 3;
/*let _marks = {};
for (let i = _min; i < _max; i++) {
_marks[i] = i + 1;
}*/
return (
<div className="col-lg-12 form-group form-material">
<div>
<div>
Fee
<span>
<i
className="icon fa-question-circle settings-help"
data-html={ true }
data-tip={ this.state.btcFeesType === 'advanced' ? 'Electrum based fee estimates may not be as accurate as bitcoinfees.earn.com.<br />It is advised to use fast/average/slow options if you want your transaction to be confirmed within 60 min time frame.' : 'Estimates are based on bitcoinfees.earn.com data.<br />Around 90% probability for a transaction to be confirmed within desired time frame.' }></i>
<ReactTooltip
effect="solid"
className="text-left" />
</span>
</div>
<div className="send-target-block">
{ this.state.btcFeesType !== 'advanced' &&
<span>Confirmation time <strong>{ _confTime[this.state.btcFeesTimeBasedStep] }</strong></span>
}
{ this.state.btcFeesType === 'advanced' &&
<span>Advanced selection</span>
}
</div>
<Slider
className="send-slider-time-based margin-bottom-70"
onChange={ this.onSliderChangeTime }
defaultValue={ this.state.btcFeesTimeBasedStep }
min={ _minTimeBased }
max={ _maxTimeBased }
dots={ true }
marks={{
0: 'fast',
1: 'average',
2: 'slow',
3: 'advanced'
}} />
{ this.state.btcFeesType === 'advanced' &&
<div>
<div className="send-target-block">Estimated to be included within the next <strong>{this.state.btcFeesAdvancedStep + 1} {(this.state.btcFeesAdvancedStep + 1) > 1 ? 'blocks' : 'block'}</strong></div>
<Slider
onChange={ this.onSliderChange }
defaultValue={ this.state.btcFeesAdvancedStep }
min={ _min }
max={ _max } />
</div>
}
{ this.state.btcFeesSize > 0 &&
<div className="margin-top-10">Fee per byte {this.state.btcFeesSize}, per KB {this.state.btcFeesSize * 1024}</div>
}
</div>
</div>
);
}
}
render() { render() {
if (this.props && if (this.props &&
this.props.ActiveCoin && this.props.ActiveCoin &&

42
react/src/components/dashboard/sendCoin/sendCoin.render.js

@ -2,6 +2,8 @@ import React from 'react';
import { translate } from '../../../translate/translate'; import { translate } from '../../../translate/translate';
import QRModal from '../qrModal/qrModal'; import QRModal from '../qrModal/qrModal';
import { isKomodoCoin } from '../../../util/coinHelper'; import { isKomodoCoin } from '../../../util/coinHelper';
import { formatValue } from '../../../util/formatValue';
import ReactTooltip from 'react-tooltip';
export const AddressListRender = function() { export const AddressListRender = function() {
return ( return (
@ -117,6 +119,7 @@ export const _SendFormRender = function() {
</span> </span>
} }
</div> </div>
{ this.renderBTCFees() }
<div className="col-lg-6 form-group form-material hide"> <div className="col-lg-6 form-group form-material hide">
<label <label
className="control-label" className="control-label"
@ -169,7 +172,7 @@ export const SendRender = function() {
); );
} else { } else {
return ( return (
<div className="col-sm-12 padding-top-10"> <div className="col-sm-12 padding-top-10 coin-send-form">
<div className="col-xlg-12 col-md-12 col-sm-12 col-xs-12"> <div className="col-xlg-12 col-md-12 col-sm-12 col-xs-12">
<div className="steps row margin-top-10"> <div className="steps row margin-top-10">
<div className={ 'step col-md-4' + (this.state.currentStep === 0 ? ' current' : '') }> <div className={ 'step col-md-4' + (this.state.currentStep === 0 ? ' current' : '') }>
@ -241,6 +244,38 @@ export const SendRender = function() {
</div> </div>
</div> </div>
} }
{ this.state.btcPreflightRes &&
<div className="row padding-top-20">
<div className="col-xs-12">
<strong>Fee</strong>
</div>
<div className="col-lg-12 col-sm-12 col-xs-12">{ formatValue(this.state.btcPreflightRes.fee * 0.00000001) } ({ this.state.btcPreflightRes.fee } sats)</div>
</div>
}
{ this.state.btcPreflightRes &&
<div className="row padding-top-20">
{ this.state.btcPreflightRes.change === 0 &&
<div className="col-lg-12 col-sm-12 col-xs-12">
<strong>Adjusted amount</strong>
<span>
<i
className="icon fa-question-circle settings-help send-btc"
data-tip="Max. available amount to spend - transaction fee"></i>
<ReactTooltip
effect="solid"
className="text-left" />
</span>
&nbsp;{ formatValue((this.state.btcPreflightRes.value * 0.00000001) - (this.state.btcPreflightRes.fee * 0.00000001)) }
</div>
}
{ this.state.btcPreflightRes.change > 0 &&
<div className="col-lg-12 col-sm-12 col-xs-12">
<strong>Total (amount + transaction fee)</strong>&nbsp;
{ formatValue((this.state.btcPreflightRes.value * 0.00000001) + (this.state.btcPreflightRes.fee * 0.00000001)) }
</div>
}
</div>
}
{ this.state.spvPreflightSendInProgress && { this.state.spvPreflightSendInProgress &&
<div className="padding-top-20">{ translate('SEND.SPV_VERIFYING') }...</div> <div className="padding-top-20">{ translate('SEND.SPV_VERIFYING') }...</div>
} }
@ -364,7 +399,10 @@ export const SendRender = function() {
<strong className="text-capitalize">{ translate('API.ERROR_SM') }</strong> <strong className="text-capitalize">{ translate('API.ERROR_SM') }</strong>
</div> </div>
{ (this.state.lastSendToResponse.result.toLowerCase().indexOf('decode error') > -1) && { (this.state.lastSendToResponse.result.toLowerCase().indexOf('decode error') > -1) &&
<div>Your history contains shielded transactions(z).<br />Please move funds to another transparent address in order to use Lite mode.</div> <div>
Your history contains shielded transactions(z).<br />
Please move funds to another transparent address in order to use Lite mode.
</div>
} }
{ this.state.lastSendToResponse.result.toLowerCase().indexOf('decode error') === -1 && { this.state.lastSendToResponse.result.toLowerCase().indexOf('decode error') === -1 &&
<div>{ this.state.lastSendToResponse.result }</div> <div>{ this.state.lastSendToResponse.result }</div>

35
react/src/components/dashboard/sendCoin/sendCoin.scss

@ -14,3 +14,38 @@
top: 20px; top: 20px;
padding: 0 11px; padding: 0 11px;
} }
.extcoin-send-form {
.send-target-block {
margin-bottom: 14px;
margin-top: 10px;
}
.rc-slider {
left: 4px;
&.send-slider-time-based {
.rc-slider-mark {
margin-top: 3px;
.rc-slider-mark-text:first-child {
left: 8px !important;
}
.rc-slider-mark-text:last-child {
left: calc(100% - 20px) !important;
}
}
}
}
}
.coin-send-form {
overflow: hidden;
.settings-help {
&.send-btc {
margin: 0 7px 0 5px;
left: inherit;
}
}
}

1
react/src/components/dashboard/walletsNav/walletsNav.render.js

@ -54,7 +54,6 @@ export const WalletsNavWithWalletRender = function() {
<i className="icon md-view-dashboard"></i> { translate('INDEX.TRANSACTIONS') } <i className="icon md-view-dashboard"></i> { translate('INDEX.TRANSACTIONS') }
</button> </button>
{ this.props.ActiveCoin && { this.props.ActiveCoin &&
this.props.ActiveCoin.coin !== 'BTC' &&
<button <button
type="button" type="button"
className="btn btn-primary waves-effect waves-light" className="btn btn-primary waves-effect waves-light"

46
react/src/components/overrides.scss

@ -333,3 +333,49 @@ select{
.cursor-default { .cursor-default {
cursor: default; cursor: default;
} }
.rc-slider {
.rc-slider-handle {
position: absolute;
margin-left: -10px;
margin-top: -7px;
width: 20px;
height: 20px;
}
.rc-slider-mark {
top: 23px;
font-size: 14px;
}
.rc-slider-rail,
.rc-slider-track {
height: 6px;
}
.rc-slider-rail {
background-color: #dcdcdc;
}
.rc-slider-track {
background-color: #51c4f9;
}
.rc-slider-handle {
border-color: #51c4f9;
}
.rc-slider-dot {
bottom: -5px;
width: 12px;
height: 12px;
}
.rc-slider-dot-active {
border-color: #51c4f9 !important;
}
.rc-slider-dot {
border-color: #dcdcdc;
}
}

1
react/src/styles/index.scss

@ -58,6 +58,7 @@
@import '../components/toaster/toaster.scss'; @import '../components/toaster/toaster.scss';
@import '~react-table/react-table.css'; @import '~react-table/react-table.css';
@import '~react-select/dist/react-select.css'; @import '~react-select/dist/react-select.css';
@import '~rc-slider/assets/index.css';
/* dex */ /* dex */
@import 'dex/main.scss'; @import 'dex/main.scss';

Loading…
Cancel
Save