mirror of
https://github.com/trezor/trezor-wallet
synced 2025-02-24 14:02:09 +00:00
Refactored whole folder structure
This commit is contained in:
parent
c447cef7ee
commit
7864c5d7bf
@ -1,105 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
pathname: string;
|
|
||||||
}
|
|
||||||
type State = {
|
|
||||||
style: {
|
|
||||||
width: number,
|
|
||||||
left: number
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Indicator extends Component<Props, State> {
|
|
||||||
reposition: () => void;
|
|
||||||
|
|
||||||
state: State;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
style: {
|
|
||||||
width: 0,
|
|
||||||
left: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.reposition = this.reposition.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResize() {
|
|
||||||
this.reposition();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.reposition();
|
|
||||||
window.addEventListener('resize', this.reposition, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('resize', this.reposition, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(newProps: Props) {
|
|
||||||
this.reposition();
|
|
||||||
}
|
|
||||||
|
|
||||||
reposition() {
|
|
||||||
const tabs = document.querySelector('.account-tabs');
|
|
||||||
if (!tabs) return;
|
|
||||||
const active = tabs.querySelector('.active');
|
|
||||||
if (!active) return;
|
|
||||||
const bounds = active.getBoundingClientRect();
|
|
||||||
|
|
||||||
const left = bounds.left - tabs.getBoundingClientRect().left;
|
|
||||||
|
|
||||||
if (this.state.style.left !== left) {
|
|
||||||
this.setState({
|
|
||||||
style: {
|
|
||||||
width: bounds.width,
|
|
||||||
left,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="indicator" style={this.state.style}>{ this.props.pathname }</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const AccountTabs = (props: any) => {
|
|
||||||
const urlParams = props.match.params;
|
|
||||||
const basePath = `/device/${urlParams.device}/network/${urlParams.network}/account/${urlParams.account}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="account-tabs">
|
|
||||||
{/* <NavLink to={ `${basePath}` }>
|
|
||||||
History
|
|
||||||
</NavLink> */}
|
|
||||||
<NavLink exact to={`${basePath}`}>
|
|
||||||
Summary
|
|
||||||
</NavLink>
|
|
||||||
<NavLink to={`${basePath}/send`}>
|
|
||||||
Send
|
|
||||||
</NavLink>
|
|
||||||
<NavLink to={`${basePath}/receive`}>
|
|
||||||
Receive
|
|
||||||
</NavLink>
|
|
||||||
{/* <NavLink to={ `${basePath}/signverify` }>
|
|
||||||
Sign & Verify
|
|
||||||
</NavLink> */}
|
|
||||||
<Indicator pathname={props.match.pathname} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AccountTabs;
|
|
@ -1,114 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Notification } from 'components/common/Notification';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
State, TrezorDevice, Action, ThunkAction,
|
|
||||||
} from 'flowtype';
|
|
||||||
import type { Account } from 'reducers/AccountsReducer';
|
|
||||||
import type { Discovery } from 'reducers/DiscoveryReducer';
|
|
||||||
|
|
||||||
export type StateProps = {
|
|
||||||
className: string;
|
|
||||||
selectedAccount: $ElementType<State, 'selectedAccount'>,
|
|
||||||
wallet: $ElementType<State, 'wallet'>,
|
|
||||||
children?: React.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DispatchProps = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Props = StateProps & DispatchProps;
|
|
||||||
|
|
||||||
const SelectedAccount = (props: Props) => {
|
|
||||||
const device = props.wallet.selectedDevice;
|
|
||||||
if (!device || !device.state) {
|
|
||||||
return (<section><Notification className="info" title="Loading device..." /></section>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountState = props.selectedAccount;
|
|
||||||
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
discovery,
|
|
||||||
} = accountState;
|
|
||||||
|
|
||||||
// account not found (yet). checking why...
|
|
||||||
if (!account) {
|
|
||||||
if (!discovery || discovery.waitingForDevice) {
|
|
||||||
if (device.connected) {
|
|
||||||
// case 1: device is connected but discovery not started yet (probably waiting for auth)
|
|
||||||
if (device.available) {
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Notification className="info" title="Loading accounts..." />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Notification
|
|
||||||
className="info"
|
|
||||||
title={`Device ${device.instanceLabel} is unavailable`}
|
|
||||||
message="Change passphrase settings to use this device"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// case 3: device is disconnected
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Notification
|
|
||||||
className="info"
|
|
||||||
title={`Device ${device.instanceLabel} is disconnected`}
|
|
||||||
message="Connect device to load accounts"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
} if (discovery.waitingForBackend) {
|
|
||||||
// case 4: backend is not working
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Notification className="warning" title="Backend not working" />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
} if (discovery.completed) {
|
|
||||||
// case 5: account not found and discovery is completed
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Notification className="warning" title="Account does not exist" />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// case 6: discovery is not completed yet
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Notification className="info" title="Loading accounts..." />
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let notification: ?React$Element<typeof Notification> = null;
|
|
||||||
if (!device.connected) {
|
|
||||||
notification = <Notification className="info" title={`Device ${device.instanceLabel} is disconnected`} />;
|
|
||||||
} else if (!device.available) {
|
|
||||||
notification = <Notification className="info" title={`Device ${device.instanceLabel} is unavailable`} message="Change passphrase settings to use this device" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discovery && !discovery.completed && !notification) {
|
|
||||||
notification = <Notification className="info" title="Loading accounts..." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={props.className}>
|
|
||||||
{ notification }
|
|
||||||
{ props.children }
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SelectedAccount;
|
|
@ -1,101 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { H2 } from 'components/common/Heading';
|
|
||||||
|
|
||||||
import Tooltip from 'rc-tooltip';
|
|
||||||
import { QRCode } from 'react-qr-svg';
|
|
||||||
|
|
||||||
import { Notification } from 'components/common/Notification';
|
|
||||||
import SelectedAccount from '../SelectedAccount';
|
|
||||||
|
|
||||||
import type { Props } from './index';
|
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
|
||||||
const StyledH2 = styled(H2)`
|
|
||||||
padding: 20px 48px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Receive = (props: Props) => {
|
|
||||||
const device = props.wallet.selectedDevice;
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
network,
|
|
||||||
discovery,
|
|
||||||
} = props.selectedAccount;
|
|
||||||
|
|
||||||
if (!device || !account || !discovery) return null;
|
|
||||||
|
|
||||||
const {
|
|
||||||
addressVerified,
|
|
||||||
addressUnverified,
|
|
||||||
} = props.receive;
|
|
||||||
|
|
||||||
let qrCode = null;
|
|
||||||
let address = `${account.address.substring(0, 20)}...`;
|
|
||||||
let className = 'address hidden';
|
|
||||||
let button = (
|
|
||||||
<button disabled={device.connected && !discovery.completed} onClick={event => props.showAddress(account.addressPath)}>
|
|
||||||
<span>Show full address</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (addressVerified || addressUnverified) {
|
|
||||||
qrCode = (
|
|
||||||
<QRCode
|
|
||||||
className="qr"
|
|
||||||
bgColor="#FFFFFF"
|
|
||||||
fgColor="#000000"
|
|
||||||
level="Q"
|
|
||||||
style={{ width: 256 }}
|
|
||||||
value={account.address}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
address = account.address;
|
|
||||||
className = addressUnverified ? 'address unverified' : 'address';
|
|
||||||
|
|
||||||
const tooltip = addressUnverified
|
|
||||||
? (<div>Unverified address.<br />{ device.connected && device.available ? 'Show on TREZOR' : 'Connect your TREZOR to verify it.' }</div>)
|
|
||||||
: (<div>{ device.connected ? 'Show on TREZOR' : 'Connect your TREZOR to verify address.' }</div>);
|
|
||||||
|
|
||||||
button = (
|
|
||||||
<Tooltip
|
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
|
||||||
overlay={tooltip}
|
|
||||||
placement="bottomRight"
|
|
||||||
>
|
|
||||||
<button className="white" onClick={event => props.showAddress(account.addressPath)}>
|
|
||||||
<span />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ver = null;
|
|
||||||
if (props.modal.opened && props.modal.windowType === 'ButtonRequest_Address') {
|
|
||||||
className = 'address verifying';
|
|
||||||
address = account.address;
|
|
||||||
ver = (<div className="confirm">Confirm address on TREZOR</div>);
|
|
||||||
button = (<div className="confirm">{ account.network } account #{ (account.index + 1) }</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<StyledH2>Receive Ethereum or tokens</StyledH2>
|
|
||||||
<div className={className}>
|
|
||||||
{ ver }
|
|
||||||
<div className="value">
|
|
||||||
{ address }
|
|
||||||
</div>
|
|
||||||
{ button }
|
|
||||||
</div>
|
|
||||||
{ qrCode }
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (props: Props) => (
|
|
||||||
<SelectedAccount {...props}>
|
|
||||||
<Receive {...props} />
|
|
||||||
</SelectedAccount>
|
|
||||||
);
|
|
@ -1,45 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { default as ReceiveActions } from 'actions/ReceiveActions';
|
|
||||||
import * as TokenActions from 'actions/TokenActions';
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
|
||||||
import type { State, Dispatch } from 'flowtype';
|
|
||||||
import Receive from './Receive';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
StateProps as BaseStateProps,
|
|
||||||
DispatchProps as BaseDispatchProps,
|
|
||||||
} from '../SelectedAccount';
|
|
||||||
|
|
||||||
type OwnProps = { }
|
|
||||||
|
|
||||||
type StateProps = BaseStateProps & {
|
|
||||||
receive: $ElementType<State, 'receive'>,
|
|
||||||
modal: $ElementType<State, 'modal'>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type DispatchProps = BaseDispatchProps & {
|
|
||||||
showAddress: typeof ReceiveActions.showAddress
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
|
|
||||||
className: 'receive',
|
|
||||||
selectedAccount: state.selectedAccount,
|
|
||||||
wallet: state.wallet,
|
|
||||||
|
|
||||||
receive: state.receive,
|
|
||||||
modal: state.modal,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
|
||||||
showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Receive);
|
|
@ -1,207 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import Select from 'react-select';
|
|
||||||
import { H2 } from 'components/common/Heading';
|
|
||||||
import { findAccountTokens } from 'reducers/TokensReducer';
|
|
||||||
import { calculate, validation } from 'actions/SendFormActions';
|
|
||||||
import { findToken } from 'reducers/TokensReducer';
|
|
||||||
import type { Token } from 'flowtype';
|
|
||||||
import AdvancedForm from './AdvancedForm';
|
|
||||||
import PendingTransactions from './PendingTransactions';
|
|
||||||
import { FeeSelectValue, FeeSelectOption } from './FeeSelect';
|
|
||||||
import SelectedAccount from '../SelectedAccount';
|
|
||||||
|
|
||||||
|
|
||||||
import type { Props } from './index';
|
|
||||||
|
|
||||||
export default class SendContainer extends Component<Props> {
|
|
||||||
componentWillReceiveProps(newProps: Props) {
|
|
||||||
calculate(this.props, newProps);
|
|
||||||
validation(newProps);
|
|
||||||
|
|
||||||
this.props.saveSessionStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SelectedAccount {...this.props}>
|
|
||||||
<Send {...this.props} />
|
|
||||||
</SelectedAccount>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledH2 = styled(H2)`
|
|
||||||
padding: 20px 48px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Send = (props: Props) => {
|
|
||||||
const device = props.wallet.selectedDevice;
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
network,
|
|
||||||
discovery,
|
|
||||||
tokens,
|
|
||||||
} = props.selectedAccount;
|
|
||||||
|
|
||||||
if (!device || !account || !discovery || !network) return null;
|
|
||||||
|
|
||||||
const {
|
|
||||||
address,
|
|
||||||
amount,
|
|
||||||
setMax,
|
|
||||||
networkSymbol,
|
|
||||||
currency,
|
|
||||||
feeLevels,
|
|
||||||
selectedFeeLevel,
|
|
||||||
gasPriceNeedsUpdate,
|
|
||||||
total,
|
|
||||||
errors,
|
|
||||||
warnings,
|
|
||||||
infos,
|
|
||||||
advanced,
|
|
||||||
data,
|
|
||||||
sending,
|
|
||||||
} = props.sendForm;
|
|
||||||
|
|
||||||
const {
|
|
||||||
onAddressChange,
|
|
||||||
onAmountChange,
|
|
||||||
onSetMax,
|
|
||||||
onCurrencyChange,
|
|
||||||
onFeeLevelChange,
|
|
||||||
updateFeeLevels,
|
|
||||||
onSend,
|
|
||||||
} = props.sendFormActions;
|
|
||||||
|
|
||||||
const fiatRate = props.fiat.find(f => f.network === network);
|
|
||||||
|
|
||||||
const tokensSelectData = tokens.map(t => ({ value: t.symbol, label: t.symbol }));
|
|
||||||
tokensSelectData.unshift({ value: network.symbol, label: network.symbol });
|
|
||||||
|
|
||||||
const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max';
|
|
||||||
|
|
||||||
let updateFeeLevelsButton = null;
|
|
||||||
if (gasPriceNeedsUpdate) {
|
|
||||||
updateFeeLevelsButton = (
|
|
||||||
<span className="update-fee-levels">Recommended fees updated. <a onClick={updateFeeLevels}>Click here to use them</a></span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let addressClassName: ?string;
|
|
||||||
if (errors.address) {
|
|
||||||
addressClassName = 'not-valid';
|
|
||||||
} else if (warnings.address) {
|
|
||||||
addressClassName = 'warning';
|
|
||||||
} else if (address.length > 0) {
|
|
||||||
addressClassName = 'valid';
|
|
||||||
}
|
|
||||||
|
|
||||||
let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending;
|
|
||||||
let buttonLabel: string = 'Send';
|
|
||||||
if (networkSymbol !== currency && amount.length > 0 && !errors.amount) {
|
|
||||||
buttonLabel += ` ${amount} ${currency.toUpperCase()}`;
|
|
||||||
} else if (networkSymbol === currency && total !== '0') {
|
|
||||||
buttonLabel += ` ${total} ${network.symbol}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!device.connected) {
|
|
||||||
buttonLabel = 'Device is not connected';
|
|
||||||
buttonDisabled = true;
|
|
||||||
} else if (!device.available) {
|
|
||||||
buttonLabel = 'Device is unavailable';
|
|
||||||
buttonDisabled = true;
|
|
||||||
} else if (!discovery.completed) {
|
|
||||||
buttonLabel = 'Loading accounts';
|
|
||||||
buttonDisabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="send-form">
|
|
||||||
<StyledH2>Send Ethereum or tokens</StyledH2>
|
|
||||||
<div className="row address-input">
|
|
||||||
<label>Address</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={address}
|
|
||||||
className={addressClassName}
|
|
||||||
onChange={event => onAddressChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
<span className="input-icon" />
|
|
||||||
{ errors.address ? (<span className="error">{ errors.address }</span>) : null }
|
|
||||||
{ warnings.address ? (<span className="warning">{ warnings.address }</span>) : null }
|
|
||||||
{ infos.address ? (<span className="info">{ infos.address }</span>) : null }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
<label>Amount</label>
|
|
||||||
<div className="amount-input">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={amount}
|
|
||||||
className={errors.amount ? 'not-valid' : null}
|
|
||||||
onChange={event => onAmountChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<a className={setMaxClassName} onClick={onSetMax}>Set max</a>
|
|
||||||
|
|
||||||
<Select
|
|
||||||
name="currency"
|
|
||||||
className="currency"
|
|
||||||
searchable={false}
|
|
||||||
clearable={false}
|
|
||||||
multi={false}
|
|
||||||
value={currency}
|
|
||||||
disabled={tokensSelectData.length < 2}
|
|
||||||
onChange={onCurrencyChange}
|
|
||||||
options={tokensSelectData}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{ errors.amount ? (<span className="error">{ errors.amount }</span>) : null }
|
|
||||||
{ warnings.amount ? (<span className="warning">{ warnings.amount }</span>) : null }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
<label>Fee{ updateFeeLevelsButton }</label>
|
|
||||||
<Select
|
|
||||||
name="fee"
|
|
||||||
className="fee"
|
|
||||||
searchable={false}
|
|
||||||
clearable={false}
|
|
||||||
value={selectedFeeLevel}
|
|
||||||
onChange={onFeeLevelChange}
|
|
||||||
valueComponent={FeeSelectValue}
|
|
||||||
optionComponent={FeeSelectOption}
|
|
||||||
disabled={networkSymbol === currency && data.length > 0}
|
|
||||||
optionClassName="fee-option"
|
|
||||||
options={feeLevels}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AdvancedForm
|
|
||||||
selectedAccount={props.selectedAccount}
|
|
||||||
sendForm={props.sendForm}
|
|
||||||
sendFormActions={props.sendFormActions}
|
|
||||||
>
|
|
||||||
<button disabled={buttonDisabled} onClick={event => onSend()}>{ buttonLabel }</button>
|
|
||||||
</AdvancedForm>
|
|
||||||
|
|
||||||
<PendingTransactions
|
|
||||||
pending={props.selectedAccount.pending}
|
|
||||||
tokens={props.selectedAccount.tokens}
|
|
||||||
network={network}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,47 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { default as SendFormActions } from 'actions/SendFormActions';
|
|
||||||
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
|
||||||
import type { State, Dispatch } from 'flowtype';
|
|
||||||
import SendForm from './SendForm';
|
|
||||||
|
|
||||||
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount';
|
|
||||||
|
|
||||||
type OwnProps = { }
|
|
||||||
|
|
||||||
export type StateProps = BaseStateProps & {
|
|
||||||
sendForm: $ElementType<State, 'sendForm'>,
|
|
||||||
fiat: $ElementType<State, 'fiat'>,
|
|
||||||
localStorage: $ElementType<State, 'localStorage'>,
|
|
||||||
children?: React.Node;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DispatchProps = BaseDispatchProps & {
|
|
||||||
sendFormActions: typeof SendFormActions,
|
|
||||||
saveSessionStorage: typeof SessionStorageActions.save
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
|
|
||||||
className: 'send-from',
|
|
||||||
selectedAccount: state.selectedAccount,
|
|
||||||
wallet: state.wallet,
|
|
||||||
|
|
||||||
sendForm: state.sendForm,
|
|
||||||
fiat: state.fiat,
|
|
||||||
localStorage: state.localStorage,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
|
||||||
sendFormActions: bindActionCreators(SendFormActions, dispatch),
|
|
||||||
saveSessionStorage: bindActionCreators(SessionStorageActions.save, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SendForm);
|
|
@ -1,66 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { H2 } from 'components/common/Heading';
|
|
||||||
import colors from 'config/colors';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: row;
|
|
||||||
background: ${colors.WHITE};
|
|
||||||
padding: 50px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledH2 = styled(H2)`
|
|
||||||
padding-bottom: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Column = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Sign = styled(Column)``;
|
|
||||||
|
|
||||||
const Verify = styled(Column)`
|
|
||||||
padding-left: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Label = styled.div`
|
|
||||||
color: ${colors.LABEL};
|
|
||||||
padding: 5px 0px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Textarea = styled.textarea`
|
|
||||||
resize: vertical;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Input = styled.input``;
|
|
||||||
|
|
||||||
const SignVerify = () => (
|
|
||||||
<Wrapper>
|
|
||||||
<Sign>
|
|
||||||
<StyledH2>Sign message</StyledH2>
|
|
||||||
<Label>Message</Label>
|
|
||||||
<Textarea rows="4" maxLength="255" />
|
|
||||||
<Label>Address</Label>
|
|
||||||
<Input type="text" />
|
|
||||||
<Label>Signature</Label>
|
|
||||||
<Textarea rows="4" maxLength="255" readOnly="readonly" />
|
|
||||||
</Sign>
|
|
||||||
<Verify>
|
|
||||||
<StyledH2>Verify message</StyledH2>
|
|
||||||
<Label>Message</Label>
|
|
||||||
<Textarea rows="4" maxLength="255" />
|
|
||||||
<Label>Address</Label>
|
|
||||||
<Input type="text" />
|
|
||||||
<Label>Signature</Label>
|
|
||||||
<Textarea rows="4" maxLength="255" />
|
|
||||||
</Verify>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default SignVerify;
|
|
@ -1,120 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { H2 } from 'components/common/Heading';
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import { Async as AsyncSelect } from 'react-select';
|
|
||||||
import Tooltip from 'rc-tooltip';
|
|
||||||
|
|
||||||
import { resolveAfter } from 'utils/promiseUtils';
|
|
||||||
import { Notification } from 'components/common/Notification';
|
|
||||||
import * as stateUtils from 'reducers/utils';
|
|
||||||
import type { NetworkToken } from 'reducers/LocalStorageReducer';
|
|
||||||
import SelectedAccount from '../SelectedAccount';
|
|
||||||
import SummaryDetails from './SummaryDetails.js';
|
|
||||||
import SummaryTokens from './SummaryTokens.js';
|
|
||||||
|
|
||||||
import type { Props } from './index';
|
|
||||||
|
|
||||||
const StyledH2 = styled(H2)`
|
|
||||||
padding: 20px 48px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Summary = (props: Props) => {
|
|
||||||
const device = props.wallet.selectedDevice;
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
network,
|
|
||||||
tokens,
|
|
||||||
pending,
|
|
||||||
} = props.selectedAccount;
|
|
||||||
|
|
||||||
// flow
|
|
||||||
if (!device || !account || !network) return null;
|
|
||||||
|
|
||||||
const tokensTooltip = (
|
|
||||||
<div className="tooltip-wrapper">
|
|
||||||
Insert token name, symbol or address to be able to send it.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
const explorerLink: string = `${network.explorer.address}${account.address}`;
|
|
||||||
|
|
||||||
const pendingAmount: BigNumber = stateUtils.getPendingAmount(pending, network.symbol);
|
|
||||||
const balance: string = new BigNumber(account.balance).minus(pendingAmount).toString(10);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StyledH2 className={`summary-header ${account.network}`}>
|
|
||||||
Account #{ parseInt(account.index) + 1 }
|
|
||||||
<a href={explorerLink} className="gray" target="_blank" rel="noreferrer noopener">See full transaction history</a>
|
|
||||||
</StyledH2>
|
|
||||||
|
|
||||||
<SummaryDetails
|
|
||||||
coin={network}
|
|
||||||
summary={props.summary}
|
|
||||||
balance={balance}
|
|
||||||
network={network.network}
|
|
||||||
fiat={props.fiat}
|
|
||||||
localStorage={props.localStorage}
|
|
||||||
onToggle={props.onDetailsToggle}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StyledH2>
|
|
||||||
Tokens
|
|
||||||
<Tooltip
|
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
|
||||||
overlay={tokensTooltip}
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<span className="what-is-it" />
|
|
||||||
</Tooltip>
|
|
||||||
</StyledH2>
|
|
||||||
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 Lahod */}
|
|
||||||
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 T01 */}
|
|
||||||
<div className="filter">
|
|
||||||
<AsyncSelect
|
|
||||||
className="token-select"
|
|
||||||
multi={false}
|
|
||||||
autoload={false}
|
|
||||||
ignoreCase
|
|
||||||
backspaceRemoves
|
|
||||||
value={null}
|
|
||||||
onChange={token => props.addToken(token, account)}
|
|
||||||
loadOptions={input => props.loadTokens(input, account.network)}
|
|
||||||
filterOptions={
|
|
||||||
(options: Array<NetworkToken>, search: string, values: Array<NetworkToken>) => options.map((o) => {
|
|
||||||
const added = tokens.find(t => t.symbol === o.symbol);
|
|
||||||
if (added) {
|
|
||||||
return {
|
|
||||||
...o,
|
|
||||||
name: `${o.name} (Already added)`,
|
|
||||||
disabled: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
valueKey="symbol"
|
|
||||||
labelKey="name"
|
|
||||||
placeholder="Search for token"
|
|
||||||
searchPromptText="Type token name or address"
|
|
||||||
noResultsText="Token not found"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SummaryTokens
|
|
||||||
pending={pending}
|
|
||||||
tokens={tokens}
|
|
||||||
removeToken={props.removeToken}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (props: Props) => (
|
|
||||||
<SelectedAccount {...props}>
|
|
||||||
<Summary {...props} />
|
|
||||||
</SelectedAccount>
|
|
||||||
);
|
|
@ -1,75 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
|
|
||||||
import type { Coin } from 'reducers/LocalStorageReducer';
|
|
||||||
import type { Props as BaseProps } from './index';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
// coin: $PropertyType<$ElementType<BaseProps, 'selectedAccount'>, 'coin'>,
|
|
||||||
coin: Coin,
|
|
||||||
summary: $ElementType<BaseProps, 'summary'>,
|
|
||||||
balance: string,
|
|
||||||
network: string,
|
|
||||||
fiat: $ElementType<BaseProps, 'fiat'>,
|
|
||||||
onToggle: $ElementType<BaseProps, 'onDetailsToggle'>
|
|
||||||
}
|
|
||||||
|
|
||||||
const SummaryDetails = (props: Props): ?React$Element<string> => {
|
|
||||||
if (!props.summary.details) {
|
|
||||||
return (
|
|
||||||
<div className="summary-details">
|
|
||||||
<div className="toggle" onClick={props.onToggle} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedCoin = props.coin;
|
|
||||||
const fiatRate = props.fiat.find(f => f.network === selectedCoin.network);
|
|
||||||
|
|
||||||
let balanceColumn = null;
|
|
||||||
let rateColumn = null;
|
|
||||||
|
|
||||||
if (fiatRate) {
|
|
||||||
const accountBalance = new BigNumber(props.balance);
|
|
||||||
const fiatValue = new BigNumber(fiatRate.value);
|
|
||||||
const fiat = accountBalance.times(fiatValue).toFixed(2);
|
|
||||||
|
|
||||||
balanceColumn = (
|
|
||||||
<div className="column">
|
|
||||||
<div className="label">Balance</div>
|
|
||||||
<div className="fiat-value">${ fiat }</div>
|
|
||||||
<div className="value">{ props.balance } { selectedCoin.symbol }</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
rateColumn = (
|
|
||||||
<div className="column">
|
|
||||||
<div className="label">Rate</div>
|
|
||||||
<div className="fiat-value">${ fiatValue.toFixed(2) }</div>
|
|
||||||
<div className="value">1.00 { selectedCoin.symbol }</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
balanceColumn = (
|
|
||||||
<div className="column">
|
|
||||||
<div className="label">Balance</div>
|
|
||||||
<div className="fiat-value">{ props.balance } { selectedCoin.symbol }</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="summary-details opened">
|
|
||||||
<div className="toggle" onClick={props.onToggle} />
|
|
||||||
<div className="content">
|
|
||||||
{ balanceColumn }
|
|
||||||
{ rateColumn }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SummaryDetails;
|
|
@ -1,54 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ColorHash from 'color-hash';
|
|
||||||
import ScaleText from 'react-scale-text';
|
|
||||||
import * as stateUtils from 'reducers/utils';
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
|
|
||||||
import type { Props as BaseProps } from './index';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
pending: $PropertyType<$ElementType<BaseProps, 'selectedAccount'>, 'pending'>,
|
|
||||||
tokens: $ElementType<BaseProps, 'tokens'>,
|
|
||||||
removeToken: $ElementType<BaseProps, 'removeToken'>
|
|
||||||
}
|
|
||||||
|
|
||||||
const SummaryTokens = (props: Props) => {
|
|
||||||
if (!props.tokens || props.tokens.length < 1) return null;
|
|
||||||
|
|
||||||
const bgColor = new ColorHash({ lightness: 0.7 });
|
|
||||||
const textColor = new ColorHash();
|
|
||||||
|
|
||||||
const tokens = props.tokens.map((token, index) => {
|
|
||||||
const iconColor = {
|
|
||||||
color: textColor.hex(token.name),
|
|
||||||
background: bgColor.hex(token.name),
|
|
||||||
borderColor: bgColor.hex(token.name),
|
|
||||||
};
|
|
||||||
|
|
||||||
const pendingAmount: BigNumber = stateUtils.getPendingAmount(props.pending, token.symbol, true);
|
|
||||||
const balance: string = new BigNumber(token.balance).minus(pendingAmount).toString(10);
|
|
||||||
return (
|
|
||||||
<div key={index} className="token">
|
|
||||||
<div className="icon" style={iconColor}>
|
|
||||||
<div className="icon-inner">
|
|
||||||
<ScaleText widthOnly><p>{ token.symbol }</p></ScaleText>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="name">{ token.name }</div>
|
|
||||||
<div className="balance">{ balance } { token.symbol }</div>
|
|
||||||
<button className="transparent" onClick={event => props.removeToken(token)} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ tokens }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SummaryTokens;
|
|
@ -1,52 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
|
||||||
import * as SummaryActions from 'actions/SummaryActions';
|
|
||||||
import * as TokenActions from 'actions/TokenActions';
|
|
||||||
|
|
||||||
import type { State, Dispatch } from 'flowtype';
|
|
||||||
import Summary from './Summary';
|
|
||||||
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount';
|
|
||||||
|
|
||||||
type OwnProps = { }
|
|
||||||
|
|
||||||
type StateProps = BaseStateProps & {
|
|
||||||
tokens: $ElementType<State, 'tokens'>,
|
|
||||||
summary: $ElementType<State, 'summary'>,
|
|
||||||
fiat: $ElementType<State, 'fiat'>,
|
|
||||||
localStorage: $ElementType<State, 'localStorage'>,
|
|
||||||
};
|
|
||||||
|
|
||||||
type DispatchProps = BaseDispatchProps & {
|
|
||||||
onDetailsToggle: typeof SummaryActions.onDetailsToggle,
|
|
||||||
addToken: typeof TokenActions.add,
|
|
||||||
loadTokens: typeof TokenActions.load,
|
|
||||||
removeToken: typeof TokenActions.remove,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
|
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => ({
|
|
||||||
className: 'summary',
|
|
||||||
selectedAccount: state.selectedAccount,
|
|
||||||
wallet: state.wallet,
|
|
||||||
|
|
||||||
tokens: state.tokens,
|
|
||||||
summary: state.summary,
|
|
||||||
fiat: state.fiat,
|
|
||||||
localStorage: state.localStorage,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
|
|
||||||
onDetailsToggle: bindActionCreators(SummaryActions.onDetailsToggle, dispatch),
|
|
||||||
addToken: bindActionCreators(TokenActions.add, dispatch),
|
|
||||||
loadTokens: bindActionCreators(TokenActions.load, dispatch),
|
|
||||||
removeToken: bindActionCreators(TokenActions.remove, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Summary);
|
|
@ -1,55 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Route, withRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
|
||||||
import type { State, Dispatch } from 'flowtype';
|
|
||||||
import Header from '../common/Header';
|
|
||||||
import Footer from '../common/Footer';
|
|
||||||
import AccountTabs from './account/AccountTabs';
|
|
||||||
import DeviceSettingsTabs from './pages/DeviceSettingsTabs';
|
|
||||||
// import AsideContainer from './aside';
|
|
||||||
import LeftNavigation from 'views/Device/components/LeftNavigation/Container';
|
|
||||||
import ModalContainer from '../modal';
|
|
||||||
import Notifications from '../common/Notification';
|
|
||||||
import Log from '../common/Log';
|
|
||||||
|
|
||||||
type WalletContainerProps = {
|
|
||||||
wallet: $ElementType<State, 'wallet'>,
|
|
||||||
children?: React.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContentProps = {
|
|
||||||
children?: React.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wallet = (props: WalletContainerProps) => (
|
|
||||||
<div className="app">
|
|
||||||
<Header />
|
|
||||||
{/* <div>{ props.wallet.online ? "ONLINE" : "OFFLINE" }</div> */}
|
|
||||||
<main>
|
|
||||||
<LeftNavigation />
|
|
||||||
<article>
|
|
||||||
<nav>
|
|
||||||
<Route path="/device/:device/network/:network/account/:account" component={AccountTabs} />
|
|
||||||
<Route path="/device/:device/device-settings" component={DeviceSettingsTabs} />
|
|
||||||
</nav>
|
|
||||||
<Notifications />
|
|
||||||
<Log />
|
|
||||||
{ props.children }
|
|
||||||
<Footer />
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
<ModalContainer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State, own: {}): WalletContainerProps => ({
|
|
||||||
wallet: state.wallet,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withRouter(
|
|
||||||
connect(mapStateToProps, null)(Wallet),
|
|
||||||
);
|
|
@ -9,6 +9,7 @@ class ErrorBoundary extends Component {
|
|||||||
componentDidCatch(error, info) {
|
componentDidCatch(error, info) {
|
||||||
// Display fallback UI
|
// Display fallback UI
|
||||||
this.setState({ hasError: true });
|
this.setState({ hasError: true });
|
||||||
|
console.error('error', error);
|
||||||
// You can also log the error to an error reporting service
|
// You can also log the error to an error reporting service
|
||||||
// logErrorToMyService(error, info);
|
// logErrorToMyService(error, info);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import { default as ReceiveActions } from 'actions/ReceiveActions';
|
|||||||
import * as TokenActions from 'actions/TokenActions';
|
import * as TokenActions from 'actions/TokenActions';
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
import Receive from './Receive';
|
import Receive from './index';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
StateProps as BaseStateProps,
|
StateProps as BaseStateProps,
|
@ -9,7 +9,7 @@ import { default as SendFormActions } from 'actions/SendFormActions';
|
|||||||
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
import * as SessionStorageActions from 'actions/SessionStorageActions';
|
||||||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
import type { State, Dispatch } from 'flowtype';
|
import type { State, Dispatch } from 'flowtype';
|
||||||
import SendForm from './SendForm';
|
import SendForm from './index';
|
||||||
|
|
||||||
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount';
|
import type { StateProps as BaseStateProps, DispatchProps as BaseDispatchProps } from '../SelectedAccount';
|
||||||
|
|
@ -0,0 +1,56 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Route, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||||
|
import type { State, Dispatch } from 'flowtype';
|
||||||
|
|
||||||
|
import Header from 'components/common/Header';
|
||||||
|
import Footer from 'components/common/Footer';
|
||||||
|
import DeviceSettingsTabs from 'components/wallet/pages/DeviceSettingsTabs';
|
||||||
|
import ModalContainer from 'components/modal';
|
||||||
|
import Notifications from 'components/common/Notification';
|
||||||
|
import Log from 'components/common/Log';
|
||||||
|
|
||||||
|
import LeftNavigation from './components/LeftNavigation/Container';
|
||||||
|
import AccountTabs from './components/Tabs';
|
||||||
|
|
||||||
|
type WalletContainerProps = {
|
||||||
|
wallet: $ElementType<State, 'wallet'>,
|
||||||
|
children?: React.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentProps = {
|
||||||
|
children?: React.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wallet = (props: WalletContainerProps) => (
|
||||||
|
<div className="app">
|
||||||
|
<Header />
|
||||||
|
{/* <div>{ props.wallet.online ? "ONLINE" : "OFFLINE" }</div> */}
|
||||||
|
<main>
|
||||||
|
<LeftNavigation />
|
||||||
|
<article>
|
||||||
|
<nav>
|
||||||
|
<Route path="/device/:device/network/:network/account/:account" component={AccountTabs} />
|
||||||
|
<Route path="/device/:device/device-settings" component={DeviceSettingsTabs} />
|
||||||
|
</nav>
|
||||||
|
<Notifications />
|
||||||
|
<Log />
|
||||||
|
{ props.children }
|
||||||
|
<Footer />
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<ModalContainer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State, own: {}): WalletContainerProps => ({
|
||||||
|
wallet: state.wallet,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withRouter(
|
||||||
|
connect(mapStateToProps, null)(Wallet),
|
||||||
|
);
|
@ -1,191 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import Tooltip from 'rc-tooltip';
|
|
||||||
import type { Props as BaseProps } from './index';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
selectedAccount: $ElementType<BaseProps, 'selectedAccount'>,
|
|
||||||
sendForm: $ElementType<BaseProps, 'sendForm'>,
|
|
||||||
sendFormActions: $ElementType<BaseProps, 'sendFormActions'>,
|
|
||||||
children?: $ElementType<BaseProps, 'children'>,
|
|
||||||
};
|
|
||||||
|
|
||||||
const AdvancedForm = (props: Props) => {
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
network,
|
|
||||||
} = props.selectedAccount;
|
|
||||||
|
|
||||||
const {
|
|
||||||
networkSymbol,
|
|
||||||
currency,
|
|
||||||
gasPrice,
|
|
||||||
gasLimit,
|
|
||||||
recommendedGasPrice,
|
|
||||||
calculatingGasLimit,
|
|
||||||
nonce,
|
|
||||||
data,
|
|
||||||
errors,
|
|
||||||
warnings,
|
|
||||||
infos,
|
|
||||||
advanced,
|
|
||||||
} = props.sendForm;
|
|
||||||
|
|
||||||
const {
|
|
||||||
toggleAdvanced,
|
|
||||||
onGasPriceChange,
|
|
||||||
onGasLimitChange,
|
|
||||||
onNonceChange,
|
|
||||||
onDataChange,
|
|
||||||
} = props.sendFormActions;
|
|
||||||
|
|
||||||
if (!advanced) {
|
|
||||||
return (
|
|
||||||
<div className="advanced-container">
|
|
||||||
<a className="advanced" onClick={toggleAdvanced}>Advanced settings</a>
|
|
||||||
{ props.children }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nonceTooltip = (
|
|
||||||
<div className="tooltip-wrapper">
|
|
||||||
TODO<br />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
let gasLimitTooltipCurrency: string;
|
|
||||||
let gasLimitTooltipValue: string;
|
|
||||||
if (networkSymbol !== currency) {
|
|
||||||
gasLimitTooltipCurrency = 'tokens';
|
|
||||||
gasLimitTooltipValue = network.defaultGasLimitTokens;
|
|
||||||
} else {
|
|
||||||
gasLimitTooltipCurrency = networkSymbol;
|
|
||||||
gasLimitTooltipValue = network.defaultGasLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gasLimitTooltip = (
|
|
||||||
<div className="tooltip-wrapper">
|
|
||||||
Gas limit is the amount of gas to send with your transaction.<br />
|
|
||||||
<span>TX fee = gas price * gas limit</span> & is paid to miners for including your TX in a block.<br />
|
|
||||||
Increasing this number will not get your TX mined faster.<br />
|
|
||||||
Default value for sending { gasLimitTooltipCurrency } is <span>{ gasLimitTooltipValue }</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const gasPriceTooltip = (
|
|
||||||
<div className="tooltip-wrapper">
|
|
||||||
Gas Price is the amount you pay per unit of gas.<br />
|
|
||||||
<span>TX fee = gas price * gas limit</span> & is paid to miners for including your TX in a block.<br />
|
|
||||||
Higher the gas price = faster transaction, but more expensive. Recommended is <span>{ recommendedGasPrice } GWEI.</span><br />
|
|
||||||
<a className="green" href="https://myetherwallet.github.io/knowledge-base/gas/what-is-gas-ethereum.html" target="_blank" rel="noreferrer noopener">Read more</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataTooltip = (
|
|
||||||
<div className="tooltip-wrapper">
|
|
||||||
Data is usually used when you send transactions to contracts.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="advanced-container opened">
|
|
||||||
<a className="advanced" onClick={toggleAdvanced}>Advanced settings</a>
|
|
||||||
<div className="row gas-row">
|
|
||||||
{/* <div className="column nonce">
|
|
||||||
<label>
|
|
||||||
Nonce
|
|
||||||
<Tooltip
|
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner"></div>}
|
|
||||||
overlay={ nonceTooltip }
|
|
||||||
placement="top">
|
|
||||||
<span className="what-is-it"></span>
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={ nonce }
|
|
||||||
onChange={ event => onNonceChange(event.target.value) } />
|
|
||||||
{ errors.nonce ? (<span className="error">{ errors.nonce }</span>) : null }
|
|
||||||
{ warnings.nonce ? (<span className="warning">{ warnings.nonce }</span>) : null }
|
|
||||||
</div> */}
|
|
||||||
<div className="column">
|
|
||||||
<label>
|
|
||||||
Gas limit
|
|
||||||
<Tooltip
|
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
|
||||||
overlay={gasLimitTooltip}
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<span className="what-is-it" />
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={gasLimit}
|
|
||||||
disabled={networkSymbol === currency && data.length > 0}
|
|
||||||
onChange={event => onGasLimitChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
{ errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null }
|
|
||||||
{ warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null }
|
|
||||||
{ calculatingGasLimit ? (<span className="info">Calculating...</span>) : null }
|
|
||||||
</div>
|
|
||||||
<div className="column">
|
|
||||||
<label>
|
|
||||||
Gas price
|
|
||||||
<Tooltip
|
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
|
||||||
overlay={gasPriceTooltip}
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<span className="what-is-it" />
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={gasPrice}
|
|
||||||
onChange={event => onGasPriceChange(event.target.value)}
|
|
||||||
/>
|
|
||||||
{ errors.gasPrice ? (<span className="error">{ errors.gasPrice }</span>) : null }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
<label>
|
|
||||||
Data
|
|
||||||
<Tooltip
|
|
||||||
arrowContent={<div className="rc-tooltip-arrow-inner" />}
|
|
||||||
overlay={dataTooltip}
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<span className="what-is-it" />
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<textarea disabled={networkSymbol !== currency} value={networkSymbol !== currency ? '' : data} onChange={event => onDataChange(event.target.value)} />
|
|
||||||
{ errors.data ? (<span className="error">{ errors.data }</span>) : null }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
{ props.children }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvancedForm;
|
|
@ -1,51 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.Node,
|
|
||||||
className: string,
|
|
||||||
isDisabled: boolean,
|
|
||||||
isFocused: boolean,
|
|
||||||
isSelected: boolean,
|
|
||||||
onFocus: Function,
|
|
||||||
onSelect: Function,
|
|
||||||
option: any,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CoinSelectOption extends React.Component<Props> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseDown(event: MouseEvent) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
this.props.onSelect(this.props.option, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter(event: MouseEvent) {
|
|
||||||
this.props.onFocus(this.props.option, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseMove(event: MouseEvent) {
|
|
||||||
if (this.props.isFocused) return;
|
|
||||||
this.props.onFocus(this.props.option, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const css = `${this.props.className} ${this.props.option.value}`;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={css}
|
|
||||||
onMouseDown={this.handleMouseDown.bind(this)}
|
|
||||||
onMouseEnter={this.handleMouseEnter.bind(this)}
|
|
||||||
onMouseMove={this.handleMouseMove.bind(this)}
|
|
||||||
title={this.props.option.label}
|
|
||||||
>
|
|
||||||
<span>{ this.props.children }</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
|
|
||||||
export const FeeSelectValue = (props: any): any => (
|
|
||||||
<div>
|
|
||||||
<div className="Select-value fee-option">
|
|
||||||
<span className="fee-value">{ props.value.value }</span>
|
|
||||||
<span className="fee-label">{ props.value.label }</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.Node,
|
|
||||||
className: string,
|
|
||||||
isDisabled: boolean,
|
|
||||||
isFocused: boolean,
|
|
||||||
isSelected: boolean,
|
|
||||||
onFocus: Function,
|
|
||||||
onSelect: Function,
|
|
||||||
option: any,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FeeSelectOption extends React.Component<Props> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseDown(event: MouseEvent) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
this.props.onSelect(this.props.option, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter(event: MouseEvent) {
|
|
||||||
this.props.onFocus(this.props.option, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseMove(event: MouseEvent) {
|
|
||||||
if (this.props.isFocused) return;
|
|
||||||
this.props.onFocus(this.props.option, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={this.props.className}
|
|
||||||
onMouseDown={this.handleMouseDown.bind(this)}
|
|
||||||
onMouseEnter={this.handleMouseEnter.bind(this)}
|
|
||||||
onMouseMove={this.handleMouseMove.bind(this)}
|
|
||||||
>
|
|
||||||
<span className="fee-value">{ this.props.option.value }</span>
|
|
||||||
<span className="fee-label">{ this.props.option.label }</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ColorHash from 'color-hash';
|
|
||||||
import { H2 } from 'components/common/Heading';
|
|
||||||
import ScaleText from 'react-scale-text';
|
|
||||||
|
|
||||||
import { findAccountTokens } from 'reducers/TokensReducer';
|
|
||||||
|
|
||||||
import type { Coin } from 'reducers/LocalStorageReducer';
|
|
||||||
import type { Token } from 'reducers/TokensReducer';
|
|
||||||
import type { Props as BaseProps } from './index';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
pending: $PropertyType<$ElementType<BaseProps, 'selectedAccount'>, 'pending'>,
|
|
||||||
tokens: $PropertyType<$ElementType<BaseProps, 'selectedAccount'>, 'tokens'>,
|
|
||||||
network: Coin
|
|
||||||
}
|
|
||||||
|
|
||||||
type Style = {
|
|
||||||
+color: string,
|
|
||||||
+background: string,
|
|
||||||
+borderColor: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const PendingTransactions = (props: Props) => {
|
|
||||||
const pending = props.pending.filter(tx => !tx.rejected);
|
|
||||||
if (pending.length < 1) return null;
|
|
||||||
|
|
||||||
const tokens: Array<Token> = props.tokens;
|
|
||||||
|
|
||||||
const bgColorFactory = new ColorHash({ lightness: 0.7 });
|
|
||||||
const textColorFactory = new ColorHash();
|
|
||||||
|
|
||||||
const pendingTxs: React$Element<string> = pending.map((tx, i) => {
|
|
||||||
let iconColor: Style;
|
|
||||||
let symbol: string;
|
|
||||||
let name: string;
|
|
||||||
const isSmartContractTx: boolean = tx.currency !== props.network.symbol;
|
|
||||||
if (isSmartContractTx) {
|
|
||||||
const token: ?Token = tokens.find(t => t.symbol === tx.currency);
|
|
||||||
if (!token) {
|
|
||||||
iconColor = {
|
|
||||||
color: '#ffffff',
|
|
||||||
background: '#000000',
|
|
||||||
borderColor: '#000000',
|
|
||||||
};
|
|
||||||
symbol = 'Unknown';
|
|
||||||
name = 'Unknown';
|
|
||||||
} else {
|
|
||||||
const bgColor: string = bgColorFactory.hex(token.name);
|
|
||||||
iconColor = {
|
|
||||||
color: textColorFactory.hex(token.name),
|
|
||||||
background: bgColor,
|
|
||||||
borderColor: bgColor,
|
|
||||||
};
|
|
||||||
symbol = token.symbol.toUpperCase();
|
|
||||||
name = token.name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
iconColor = {
|
|
||||||
color: textColorFactory.hex(tx.network),
|
|
||||||
background: bgColorFactory.hex(tx.network),
|
|
||||||
borderColor: bgColorFactory.hex(tx.network),
|
|
||||||
};
|
|
||||||
symbol = props.network.symbol;
|
|
||||||
name = props.network.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={i} className="tx">
|
|
||||||
<div className="icon" style={iconColor}>
|
|
||||||
<div className="icon-inner">
|
|
||||||
<ScaleText widthOnly><p>{ symbol }</p></ScaleText>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="name">
|
|
||||||
<a href={`${props.network.explorer.tx}${tx.id}`} target="_blank" rel="noreferrer noopener">{ name }</a>
|
|
||||||
</div>
|
|
||||||
<div className="amount">{ isSmartContractTx ? tx.amount : tx.total } { symbol }</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pending-transactions">
|
|
||||||
<H2>Pending transactions</H2>
|
|
||||||
{ pendingTxs }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PendingTransactions;
|
|
@ -6,20 +6,23 @@ import { ConnectedRouter } from 'react-router-redux';
|
|||||||
|
|
||||||
import ErrorBoundary from 'support/ErrorBoundary';
|
import ErrorBoundary from 'support/ErrorBoundary';
|
||||||
|
|
||||||
import WalletContainer from 'components/wallet';
|
import WalletContainer from 'views/Device';
|
||||||
import BootloaderContainer from 'components/wallet/pages/Bootloader';
|
import BootloaderContainer from 'components/wallet/pages/Bootloader';
|
||||||
import InitializeContainer from 'components/wallet/pages/Initialize';
|
import InitializeContainer from 'components/wallet/pages/Initialize';
|
||||||
import AcquireContainer from 'components/wallet/pages/Acquire';
|
import AcquireContainer from 'components/wallet/pages/Acquire';
|
||||||
import UnreadableDeviceContainer from 'components/wallet/pages/UnreadableDevice';
|
import UnreadableDeviceContainer from 'components/wallet/pages/UnreadableDevice';
|
||||||
|
|
||||||
import DashboardContainer from 'components/wallet/pages/Dashboard';
|
import DashboardContainer from 'components/wallet/pages/Dashboard';
|
||||||
import SummaryContainer from 'components/wallet/account/summary';
|
|
||||||
import SendFormContainer from 'components/wallet/account/send';
|
|
||||||
import ReceiveContainer from 'components/wallet/account/receive';
|
|
||||||
import SignVerifyContainer from 'components/wallet/account/sign/SignVerify';
|
|
||||||
import DeviceSettingsContainer from 'components/wallet/pages/DeviceSettings';
|
import DeviceSettingsContainer from 'components/wallet/pages/DeviceSettings';
|
||||||
import WalletSettingsContainer from 'components/wallet/pages/WalletSettings';
|
import WalletSettingsContainer from 'components/wallet/pages/WalletSettings';
|
||||||
import LandingContainer from 'views/Landing/Container';
|
import LandingContainer from 'views/Landing/Container';
|
||||||
|
|
||||||
|
import SignVerifyContainer from './Device/components/Sign';
|
||||||
|
import ReceiveContainer from './Device/components/Receive/Container';
|
||||||
|
import SendFormContainer from './Device/components/Send/Container';
|
||||||
|
import SummaryContainer from './Device/components/Summary/Container';
|
||||||
|
|
||||||
import store, { history } from '../store';
|
import store, { history } from '../store';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
@ -30,8 +33,8 @@ const App = () => (
|
|||||||
<Route exact path="/bridge" component={LandingContainer} />
|
<Route exact path="/bridge" component={LandingContainer} />
|
||||||
<Route exact path="/import" component={LandingContainer} />
|
<Route exact path="/import" component={LandingContainer} />
|
||||||
<Route>
|
<Route>
|
||||||
<WalletContainer>
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<WalletContainer>
|
||||||
<Route exact path="/settings" component={WalletSettingsContainer} />
|
<Route exact path="/settings" component={WalletSettingsContainer} />
|
||||||
<Route exact path="/device/:device/" component={DashboardContainer} />
|
<Route exact path="/device/:device/" component={DashboardContainer} />
|
||||||
<Route exact path="/device/:device/network/:network" component={DashboardContainer} />
|
<Route exact path="/device/:device/network/:network" component={DashboardContainer} />
|
||||||
@ -45,8 +48,8 @@ const App = () => (
|
|||||||
<Route path="/device/:device/network/:network/account/:account/send/override" component={SendFormContainer} />
|
<Route path="/device/:device/network/:network/account/:account/send/override" component={SendFormContainer} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/receive" component={ReceiveContainer} />
|
<Route path="/device/:device/network/:network/account/:account/receive" component={ReceiveContainer} />
|
||||||
<Route path="/device/:device/network/:network/account/:account/signverify" component={SignVerifyContainer} />
|
<Route path="/device/:device/network/:network/account/:account/signverify" component={SignVerifyContainer} />
|
||||||
</ErrorBoundary>
|
</WalletContainer>
|
||||||
</WalletContainer>
|
</ErrorBoundary>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
|
Loading…
Reference in New Issue
Block a user