Skip to content

Commit 57d00b1

Browse files
authored
Merge pull request skycoin#567 from Senyoret1/USD
Allow to add amounts in USD when creating transactions
2 parents 4292550 + cb61f68 commit 57d00b1

13 files changed

+312
-29
lines changed

src/app/components/layout/double-button/double-button.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<div class="-buttons-container {{ className }}">
2-
<app-button class="-toggle dark-button"
2+
<app-button class="-toggle dark-button -small-button"
33
[disabled]="activeButton !== doubleButtonActive.LeftButton"
44
(action)="onClick(doubleButtonActive.LeftButton)"
55
[forceEmitEvents]="true">
66
{{ leftButtonText }}
77
</app-button>
8-
<app-button class="-toggle dark-button"
8+
<app-button class="-toggle dark-button -small-button"
99
[disabled]="activeButton !== doubleButtonActive.RightButton"
1010
(action)="onClick(doubleButtonActive.RightButton)"
1111
[forceEmitEvents]="true">

src/app/components/layout/double-button/double-button.component.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,17 @@
6262
}
6363
}
6464
}
65+
66+
.small {
67+
&.-buttons-container {
68+
padding: 2px;
69+
line-height: 0px;
70+
}
71+
72+
.-small-button ::ng-deep button {
73+
padding: 0 10px 0 10px;
74+
min-width: 70px;
75+
font-size: 9px;
76+
min-height: 16px;
77+
}
78+
}

src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.html

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,19 @@
9191
</ng-container>
9292

9393
<div class="form-field">
94-
<label for="destination0">
94+
<label for="destination0" class="destinations-label">
9595
{{ 'send.destinations-label' | translate }}
9696
<mat-icon [matTooltip]="('send.destinations-help' + (this.autoHours ? '1' : '2')) | translate">help</mat-icon>
9797
</label>
98+
<div class="coin-selector-container" *ngIf="price" [ngClass]="{ disabled: busy }">
99+
<app-double-button
100+
[leftButtonText]="currentCoin.coinSymbol"
101+
[rightButtonText]="'common.usd' | translate"
102+
className="light small"
103+
[activeButton]="selectedCurrency"
104+
(onStateChange)="changeActiveCurrency($event)"
105+
></app-double-button>
106+
</div>
98107

99108
<div formArrayName="destinations" *ngFor="let dest of destControls; let i = index;" class="-destination">
100109
<div [formGroupName]="i" class="row -inner-container">
@@ -113,7 +122,16 @@
113122
</label>
114123
<div class="-input-addon">
115124
<input formControlName="coins" [id]="'amount' + i">
116-
<span>{{ currentCoin.coinSymbol }}</span>
125+
<span>{{ selectedCurrency === doubleButtonActive.LeftButton ? currentCoin.coinSymbol : ('common.usd' | translate) }}</span>
126+
</div>
127+
<div class="coins-value-label" *ngIf="price">
128+
<span *ngIf="values[i] < 0">{{ 'send.invalid-amount' | translate }}</span>
129+
<span *ngIf="values[i] >= 0 && selectedCurrency === doubleButtonActive.LeftButton">
130+
&#x007e; {{ values[i] | number:'1.0-2' }} {{ 'common.usd' | translate }}
131+
</span>
132+
<span *ngIf="values[i] >= 0 && selectedCurrency === doubleButtonActive.RightButton">
133+
&#x007e; {{ values[i] | number:('1.0-' + blockchainService.currentMaxDecimals) }} {{ currentCoin.coinSymbol }}
134+
</span>
117135
</div>
118136
</div>
119137
<div class="col-lg-3 col-md-4">

src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,7 @@ label mat-icon {
275275
font-size: 13px;
276276
}
277277
}
278+
279+
.destinations-label {
280+
display: inline-block;
281+
}

src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ import { MatSnackBarModule } from '@angular/material';
44
import { FormBuilder } from '@angular/forms';
55

66
import { SendFormAdvancedComponent } from './send-form-advanced.component';
7-
import { MockTranslatePipe, MockWalletService, MockSpendingService, MockCoinService, MockBlockchainService, MockCustomMatDialogService, MockNavBarService } from '../../../../utils/test-mocks';
87
import { WalletService } from '../../../../services/wallet/wallet.service';
98
import { SpendingService } from '../../../../services/wallet/spending.service';
109
import { CoinService } from '../../../../services/coin.service';
1110
import { BlockchainService } from '../../../../services/blockchain.service';
1211
import { CustomMatDialogService } from '../../../../services/custom-mat-dialog.service';
1312
import { NavBarService } from '../../../../services/nav-bar.service';
13+
import { PriceService } from '../../../../services/price.service';
14+
import { MockTranslatePipe,
15+
MockWalletService,
16+
MockSpendingService,
17+
MockCoinService,
18+
MockBlockchainService,
19+
MockCustomMatDialogService,
20+
MockNavBarService,
21+
MockPriceService } from '../../../../utils/test-mocks';
1422

1523
describe('SendFormAdvancedComponent', () => {
1624
let component: SendFormAdvancedComponent;
@@ -29,6 +37,7 @@ describe('SendFormAdvancedComponent', () => {
2937
{ provide: BlockchainService, useClass: MockBlockchainService },
3038
{ provide: CustomMatDialogService, useClass: MockCustomMatDialogService },
3139
{ provide: NavBarService, useClass: MockNavBarService },
40+
{ provide: PriceService, useClass: MockPriceService },
3241
]
3342
})
3443
.compileComponents();

src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.ts

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import { CustomMatDialogService } from '../../../../services/custom-mat-dialog.s
1919
import { WalletService } from '../../../../services/wallet/wallet.service';
2020
import { BaseCoin } from '../../../../coins/basecoin';
2121
import { CoinService } from '../../../../services/coin.service';
22+
import { DoubleButtonActive } from '../../../layout/double-button/double-button.component';
23+
import { PriceService } from '../../../../services/price.service';
24+
import { SendFormComponent } from '../send-form/send-form.component';
2225

2326
@Component({
2427
selector: 'app-send-form-advanced',
@@ -44,10 +47,15 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
4447
autoShareValue = '0.5';
4548
previewTx: boolean;
4649
currentCoin: BaseCoin;
50+
doubleButtonActive = DoubleButtonActive;
51+
selectedCurrency = DoubleButtonActive.LeftButton;
52+
values: number[];
53+
price: number;
4754

4855
private subscriptions: Subscription;
4956
private getOutputsSubscriptions: ISubscription;
5057
private unlockSubscription: ISubscription;
58+
private destinationSubscriptions: ISubscription[] = [];
5159

5260
constructor(
5361
public walletService: WalletService,
@@ -58,6 +66,7 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
5866
private navbarService: NavBarService,
5967
private blockchainService: BlockchainService,
6068
private coinService: CoinService,
69+
private priceService: PriceService,
6170
) { }
6271

6372
ngOnInit() {
@@ -121,6 +130,11 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
121130
})
122131
);
123132

133+
this.subscriptions.add(this.priceService.price.subscribe(price => {
134+
this.price = price;
135+
this.updateValues();
136+
}));
137+
124138
if (this.formData) {
125139
this.fillForm();
126140
}
@@ -131,6 +145,7 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
131145
this.subscriptions.unsubscribe();
132146
this.navbarService.hideSwitch();
133147
this.snackbar.dismiss();
148+
this.destinationSubscriptions.forEach(s => s.unsubscribe());
134149
}
135150

136151
preview() {
@@ -143,6 +158,53 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
143158
this.unlockAndSend();
144159
}
145160

161+
changeActiveCurrency(value) {
162+
this.selectedCurrency = value;
163+
this.updateValues();
164+
(this.form.get('destinations') as FormArray).updateValueAndValidity();
165+
}
166+
167+
private updateValues() {
168+
if (!this.price) {
169+
this.values = null;
170+
171+
return;
172+
}
173+
174+
this.values = [];
175+
176+
this.destControls.forEach((dest, i) => {
177+
const value = dest.get('coins').value !== undefined ? dest.get('coins').value.replace(' ', '=') : '';
178+
179+
if (isNaN(value) || value.trim() === '' || parseFloat(value) <= 0 || value * 1 === 0) {
180+
this.values[i] = -1;
181+
182+
return;
183+
}
184+
185+
const parts = value.split('.');
186+
if (this.selectedCurrency === DoubleButtonActive.LeftButton) {
187+
if (parts.length === 2 && parts[1].length > this.blockchainService.currentMaxDecimals) {
188+
this.values[i] = -1;
189+
190+
return;
191+
}
192+
} else {
193+
if (parts.length === 2 && parts[1].length > SendFormComponent.MaxUsdDecimal) {
194+
this.values[i] = -1;
195+
196+
return;
197+
}
198+
}
199+
200+
if (this.selectedCurrency === DoubleButtonActive.LeftButton) {
201+
this.values[i] = new BigNumber(value).multipliedBy(this.price).decimalPlaces(2).toNumber();
202+
} else {
203+
this.values[i] = new BigNumber(value).dividedBy(this.price).decimalPlaces(this.blockchainService.currentMaxDecimals).toNumber();
204+
}
205+
});
206+
}
207+
146208
unlockAndSend() {
147209
if (!this.form.valid || this.button.isLoading()) {
148210
return;
@@ -191,11 +253,16 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
191253
addDestination() {
192254
const destinations = this.form.get('destinations') as FormArray;
193255
destinations.push(this.createDestinationFormGroup());
256+
this.updateValues();
194257
}
195258

196259
removeDestination(index) {
197260
const destinations = this.form.get('destinations') as FormArray;
198261
destinations.removeAt(index);
262+
263+
this.destinationSubscriptions[index].unsubscribe();
264+
this.destinationSubscriptions.splice(index, 1);
265+
this.updateValues();
199266
}
200267

201268
setShareValue(event) {
@@ -241,9 +308,10 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
241308
}
242309

243310
this.destControls.forEach((destControl, i) => {
244-
['address', 'coins', 'hours'].forEach(name => {
311+
['address', 'hours'].forEach(name => {
245312
destControl.get(name).setValue(this.formData.form.destinations[i][name]);
246313
});
314+
destControl.get('coins').setValue(this.formData.form.destinations[i].originalAmount);
247315
});
248316

249317
if (this.formData.form.hoursSelection.type === HoursSelectionTypes.Auto) {
@@ -263,6 +331,8 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
263331

264332
this.form.get('outputs').setValue(this.formData.form.outputs);
265333
}
334+
335+
this.selectedCurrency = this.formData.form.currency;
266336
}
267337

268338
addressCompare(a, b) {
@@ -305,8 +375,14 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
305375
if (name === 'coins') {
306376
const parts = value.split('.');
307377

308-
if (parts.length === 2 && parts[1].length > this.blockchainService.currentMaxDecimals) {
309-
return true;
378+
if (this.selectedCurrency === DoubleButtonActive.LeftButton) {
379+
if (parts.length === 2 && parts[1].length > this.blockchainService.currentMaxDecimals) {
380+
return true;
381+
}
382+
} else {
383+
if (parts.length === 2 && parts[1].length > SendFormComponent.MaxUsdDecimal) {
384+
return true;
385+
}
310386
}
311387
} else if (name === 'hours') {
312388
if (Number(value) < 1 || parseInt(value, 10) !== parseFloat(value)) {
@@ -325,7 +401,12 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
325401
this.updateAvailableBalance();
326402

327403
let destinationsCoins = new BigNumber(0);
328-
this.destControls.map(control => destinationsCoins = destinationsCoins.plus(control.value.coins));
404+
if (this.selectedCurrency === DoubleButtonActive.LeftButton) {
405+
this.destControls.map(control => destinationsCoins = destinationsCoins.plus(control.value.coins));
406+
} else {
407+
this.updateValues();
408+
this.values.map(value => destinationsCoins = destinationsCoins.plus(value));
409+
}
329410
let destinationsHours = new BigNumber(0);
330411
if (!this.autoHours) {
331412
this.destControls.map(control => destinationsHours = destinationsHours.plus(control.value.hours));
@@ -339,11 +420,17 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
339420
}
340421

341422
private createDestinationFormGroup() {
342-
return this.formBuilder.group({
423+
const group = this.formBuilder.group({
343424
address: '',
344425
coins: '',
345426
hours: '',
346427
});
428+
429+
this.destinationSubscriptions.push(group.get('coins').valueChanges.subscribe(value => {
430+
this.updateValues();
431+
}));
432+
433+
return group;
347434
}
348435

349436
private createTransaction() {
@@ -386,6 +473,7 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
386473
autoOptions: this.autoOptions,
387474
allUnspentOutputs: this.loadingUnspentOutputs ? null : this.allUnspentOutputs,
388475
outputs: this.form.get('outputs').value,
476+
currency: this.selectedCurrency,
389477
},
390478
amount: amount,
391479
to: this.destinations.map(d => d.address),
@@ -427,10 +515,11 @@ export class SendFormAdvancedComponent implements OnInit, OnDestroy {
427515
}
428516

429517
private get destinations(): Destination[] {
430-
return this.destControls.map(destControl => {
518+
return this.destControls.map((destControl, i) => {
431519
const destination = {
432520
address: destControl.get('address').value,
433-
coins: new BigNumber(destControl.get('coins').value),
521+
coins: this.selectedCurrency === DoubleButtonActive.LeftButton ? new BigNumber(destControl.get('coins').value) : new BigNumber(this.values[i].toString()),
522+
originalAmount: destControl.get('coins').value,
434523
};
435524

436525
if (!this.autoHours) {

src/app/components/pages/send-skycoin/send-form/send-form.component.html

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,26 @@
2222
<input formControlName="address" id="address" [placeholder]="'send.recipient-address' | translate" >
2323
</div>
2424
<div class="form-field">
25-
<label for="amount">{{ 'send.amount-label' | translate }}</label>
26-
<input formControlName="amount" id="amount" [placeholder]="currentCoin.coinSymbol" (keydown.enter)="onVerify($event)">
25+
<label for="amount" class="amount-label">{{ 'send.amount-label' | translate }}</label>
26+
<div class="coin-selector-container" *ngIf="price" [ngClass]="{ disabled: busy }">
27+
<app-double-button
28+
[leftButtonText]="currentCoin.coinSymbol"
29+
[rightButtonText]="'common.usd' | translate"
30+
className="light small"
31+
[activeButton]="selectedCurrency"
32+
(onStateChange)="changeActiveCurrency($event)"
33+
></app-double-button>
34+
</div>
35+
<input formControlName="amount" id="amount" [placeholder]="selectedCurrency === doubleButtonActive.LeftButton ? currentCoin.coinSymbol : ('common.usd' | translate)" (keydown.enter)="onVerify($event)">
36+
<div class="coins-value-label" [ngClass]="{ red: value >= 0 && valueGreaterThanBalance }" *ngIf="price">
37+
<span *ngIf="value < 0">{{ 'send.invalid-amount' | translate }}</span>
38+
<span *ngIf="value >= 0 && selectedCurrency === doubleButtonActive.LeftButton">
39+
&#x007e; {{ value | number:'1.0-2' }} {{ 'common.usd' | translate }}
40+
</span>
41+
<span *ngIf="value >= 0 && selectedCurrency === doubleButtonActive.RightButton">
42+
&#x007e; {{ value | number:('1.0-' + blockchainService.currentMaxDecimals) }} {{ currentCoin.coinSymbol }}
43+
</span>
44+
</div>
2745
</div>
2846

2947
<div class="form-field -on-small-and-below-only" *ngIf="showSlowMobileInfo">
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
.-buttons {
22
text-align: center;
33
}
4+
5+
.amount-label {
6+
display: inline-block;
7+
}

src/app/components/pages/send-skycoin/send-form/send-form.component.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@ import { WalletService } from '../../../../services/wallet/wallet.service';
88
import { SpendingService } from '../../../../services/wallet/spending.service';
99
import { CoinService } from '../../../../services/coin.service';
1010
import { BlockchainService } from '../../../../services/blockchain.service';
11-
import { MockTranslatePipe, MockWalletService, MockSpendingService, MockCoinService, MockBlockchainService, MockCustomMatDialogService, MockNavBarService } from '../../../../utils/test-mocks';
1211
import { CustomMatDialogService } from '../../../../services/custom-mat-dialog.service';
1312
import { NavBarService } from '../../../../services/nav-bar.service';
13+
import { PriceService } from '../../../../services/price.service';
14+
import { MockTranslatePipe,
15+
MockWalletService,
16+
MockSpendingService,
17+
MockCoinService,
18+
MockBlockchainService,
19+
MockCustomMatDialogService,
20+
MockNavBarService,
21+
MockPriceService } from '../../../../utils/test-mocks';
1422

1523
describe('SendFormComponent', () => {
1624
let component: SendFormComponent;
@@ -29,6 +37,7 @@ describe('SendFormComponent', () => {
2937
{ provide: BlockchainService, useClass: MockBlockchainService },
3038
{ provide: CustomMatDialogService, useClass: MockCustomMatDialogService },
3139
{ provide: NavBarService, useClass: MockNavBarService },
40+
{ provide: PriceService, useClass: MockPriceService },
3241
]
3342
}).compileComponents();
3443
}));

0 commit comments

Comments
 (0)