Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap UX Cleanup #339

Merged
merged 11 commits into from
Nov 14, 2017
26 changes: 25 additions & 1 deletion common/containers/Tabs/Swap/components/CurrencySwap.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import "common/sass/variables";
@import 'common/sass/variables';

.CurrencySwap {
text-align: center;
Expand All @@ -13,6 +13,30 @@
display: inline-block;
vertical-align: top;
}
&-input-group {
display: inline-block;
}
&-error-message {
display: block;
min-height: 25px;
color: $brand-danger;
text-align: left;
}
&-inner-wrap {
display: block;
}
@media (min-width: $screen-xs-min) {
&-inner-wrap {
display: flex;
align-items: center;
justify-content: center;
}
}
&-dropdown {
display: inline-block;
margin-top: 0.6rem;
margin-bottom: 0.6rem;
}

&-input {
width: 100%;
Expand Down
255 changes: 182 additions & 73 deletions common/containers/Tabs/Swap/components/CurrencySwap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import translate from 'translations';
import { combineAndUpper, toFixedIfLarger } from 'utils/formatters';
import './CurrencySwap.scss';
import { Dropdown } from 'components/ui';
import Spinner from 'components/ui/Spinner';

export interface StateProps {
bityRates: any;
Expand All @@ -36,6 +37,8 @@ export interface ActionProps {
interface State {
disabled: boolean;
showedMinMaxError: boolean;
originErr: string;
destinationErr: string;
}

export default class CurrencySwap extends Component<
Expand All @@ -44,9 +47,31 @@ export default class CurrencySwap extends Component<
> {
public state = {
disabled: true,
showedMinMaxError: false
showedMinMaxError: false,
originErr: '',
destinationErr: ''
};

public componentWillReceiveProps(newProps) {
const {
originAmount,
originKind,
destinationKind,
destinationAmount
} = newProps;
if (
originKind !== this.props.originKind ||
destinationKind !== this.props.destinationKind
) {
this.setDisabled(
originAmount,
originKind,
destinationKind,
destinationAmount
);
}
}

public isMinMaxValid = (amount, kind) => {
let bityMin;
let bityMax;
Expand All @@ -70,38 +95,86 @@ export default class CurrencySwap extends Component<
return !(hasOriginAmountAndDestinationAmount && minMaxIsValid);
};

public setDisabled(originAmount, originKind, destinationAmount) {
public setDisabled(
originAmount,
originKind,
destinationKind,
destinationAmount
) {
const disabled = this.isDisabled(
originAmount,
originKind,
destinationAmount
);

if (disabled && originAmount && !this.state.showedMinMaxError) {
if (disabled && originAmount) {
const { bityRates } = this.props;
const ETHMin = generateKindMin(bityRates.BTCETH, 'ETH');
const ETHMax = generateKindMax(bityRates.BTCETH, 'ETH');
const REPMin = generateKindMin(bityRates.BTCREP, 'REP');

const notificationMessage = `
Minimum amount ${bityConfig.BTCMin} BTC,
${toFixedIfLarger(ETHMin, 3)} ETH.
Max amount ${bityConfig.BTCMax} BTC,
${toFixedIfLarger(ETHMax, 3)} ETH, or
${toFixedIfLarger(REPMin, 3)} REP
`;

this.setState(
{
disabled,
showedMinMaxError: true
},
() => {
this.props.showNotification('danger', notificationMessage, 10000);
const getRates = kind => {
let minAmount;
let maxAmount;
switch (kind) {
case 'BTC':
minAmount = toFixedIfLarger(bityConfig.BTCMin, 3);
maxAmount = toFixedIfLarger(bityConfig.BTCMax, 3);
break;
case 'ETH':
minAmount = toFixedIfLarger(ETHMin, 3);
maxAmount = toFixedIfLarger(ETHMax, 3);
break;
case 'REP':
minAmount = toFixedIfLarger(REPMin, 3);
break;
default:
if (this.state.showedMinMaxError) {
this.setState(
{
showedMinMaxError: true
},
() => {
this.props.showNotification(
'danger',
"Couldn't get match currency kind. Something went terribly wrong",
10000
);
}
);
}
}
return { minAmount, maxAmount };
};

const createErrString = (kind, amount, rate) => {
let errString;
if (amount > rate.maxAmount) {
errString = `Maximum ${kind} is ${rate.maxAmount} ${kind}`;
} else {
errString = `Minimum ${kind} is ${rate.minAmount} ${kind}`;
}

return errString;
};
const originRate = getRates(originKind);
const destinationRate = getRates(destinationKind);
const originErr = createErrString(originKind, originAmount, originRate);
const destinationErr = createErrString(
destinationKind,
destinationAmount,
destinationRate
);

this.setState({
originErr,
destinationErr,
disabled: true
});
} else {
this.setState({
originErr: '',
destinationErr: '',
disabled
});
}
Expand All @@ -114,7 +187,12 @@ export default class CurrencySwap extends Component<
public setOriginAndDestinationToNull = () => {
this.props.originAmountSwap(null);
this.props.destinationAmountSwap(null);
this.setDisabled(null, this.props.originKind, null);
this.setDisabled(
null,
this.props.originKind,
this.props.destinationKind,
null
);
};

public onChangeOriginAmount = (
Expand All @@ -129,7 +207,12 @@ export default class CurrencySwap extends Component<
this.props.originAmountSwap(originAmountAsNumber);
const destinationAmount = originAmountAsNumber * bityRate;
this.props.destinationAmountSwap(destinationAmount);
this.setDisabled(originAmountAsNumber, originKind, destinationAmount);
this.setDisabled(
originAmountAsNumber,
originKind,
destinationKind,
destinationAmount
);
} else {
this.setOriginAndDestinationToNull();
}
Expand All @@ -147,7 +230,12 @@ export default class CurrencySwap extends Component<
const bityRate = this.props.bityRates[pairNameReversed];
const originAmount = destinationAmountAsNumber * bityRate;
this.props.originAmountSwap(originAmount);
this.setDisabled(originAmount, originKind, destinationAmountAsNumber);
this.setDisabled(
originAmount,
originKind,
destinationKind,
destinationAmountAsNumber
);
} else {
this.setOriginAndDestinationToNull();
}
Expand All @@ -160,69 +248,90 @@ export default class CurrencySwap extends Component<
originKind,
destinationKind,
destinationKindOptions,
originKindOptions
originKindOptions,
bityRates
} = this.props;

const { originErr, destinationErr } = this.state;

const OriginKindDropDown = Dropdown as new () => Dropdown<
typeof originKind
>;
const DestinationKindDropDown = Dropdown as new () => Dropdown<
typeof destinationKind
>;

const pairName = combineAndUpper(originKind, destinationKind);
const bityLoaded = bityRates[pairName];
return (
<article className="CurrencySwap">
<h1 className="CurrencySwap-title">{translate('SWAP_init_1')}</h1>

<div className="form-inline">
<input
className={`CurrencySwap-input form-control ${String(
originAmount
) !== '' && this.isMinMaxValid(originAmount, originKind)
? 'is-valid'
: 'is-invalid'}`}
type="number"
placeholder="Amount"
value={originAmount || originAmount === 0 ? originAmount : ''}
onChange={this.onChangeOriginAmount}
/>

<OriginKindDropDown
ariaLabel={`change origin kind. current origin kind ${originKind}`}
options={originKindOptions}
value={originKind}
onChange={this.props.originKindSwap}
size="smr"
color="default"
/>

<h1 className="CurrencySwap-divider">{translate('SWAP_init_2')}</h1>

<input
className={`CurrencySwap-input form-control ${String(
destinationAmount
) !== '' && this.isMinMaxValid(originAmount, originKind)
? 'is-valid'
: 'is-invalid'}`}
type="number"
placeholder="Amount"
value={
destinationAmount || destinationAmount === 0
? destinationAmount
: ''
}
onChange={this.onChangeDestinationAmount}
/>

<DestinationKindDropDown
ariaLabel={`change destination kind. current destination kind ${destinationKind}`}
options={destinationKindOptions}
value={destinationKind}
onChange={this.props.destinationKindSwap}
size="smr"
color="default"
/>
</div>
{bityLoaded ? (
<div className="form-inline CurrencySwap-inner-wrap">
<div className="CurrencySwap-input-group">
<span className="CurrencySwap-error-message">{originErr}</span>
<input
className={`CurrencySwap-input form-control ${
String(originAmount) !== '' &&
this.isMinMaxValid(originAmount, originKind)
? 'is-valid'
: 'is-invalid'
}`}
type="number"
placeholder="Amount"
value={originAmount || originAmount === 0 ? originAmount : ''}
onChange={this.onChangeOriginAmount}
/>
<div className="CurrencySwap-dropdown">
<OriginKindDropDown
ariaLabel={`change origin kind. current origin kind ${
originKind
}`}
options={originKindOptions}
value={originKind}
onChange={this.props.originKindSwap}
size="smr"
color="default"
/>
</div>
</div>
<h1 className="CurrencySwap-divider">{translate('SWAP_init_2')}</h1>
<div className="CurrencySwap-input-group">
<span className="CurrencySwap-error-message">
{destinationErr}
</span>
<input
className={`CurrencySwap-input form-control ${
String(destinationAmount) !== '' &&
this.isMinMaxValid(originAmount, originKind)
? 'is-valid'
: 'is-invalid'
}`}
type="number"
placeholder="Amount"
value={
destinationAmount || destinationAmount === 0
? destinationAmount
: ''
}
onChange={this.onChangeDestinationAmount}
/>
<div className="CurrencySwap-dropdown">
<DestinationKindDropDown
ariaLabel={`change destination kind. current destination kind ${
destinationKind
}`}
options={destinationKindOptions}
value={destinationKind}
onChange={this.props.destinationKindSwap}
size="smr"
color="default"
/>
</div>
</div>
</div>
) : (
<Spinner />
)}

<div className="CurrencySwap-submit">
<SimpleButton
Expand Down
6 changes: 2 additions & 4 deletions common/containers/Tabs/Swap/components/SwapInfoHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ export default class SwapInfoHeader extends Component<SwapInfoHeaderProps, {}> {
{/*Amount to send*/}
{!this.isExpanded() && (
<div className={this.computedClass()}>
<h3 className="SwapInfo-details-block-value">
{` ${originAmount} ${originKind}`}
</h3>
<h3 className="SwapInfo-details-block-value">{` ${originAmount} ${originKind}`}</h3>
<p className="SwapInfo-details-block-label">
{translate('SEND_amount')}
</p>
Expand Down Expand Up @@ -113,7 +111,7 @@ export default class SwapInfoHeader extends Component<SwapInfoHeaderProps, {}> {
{`${computedOriginDestinationRatio &&
toFixedIfLarger(
computedOriginDestinationRatio
)} ${originKind}/${destinationKind}`}
)} ${destinationKind}/${originKind}`}
</h3>
<p className="SwapInfo-details-block-label">
{translate('SWAP_your_rate')}
Expand Down