Unverified Commit 896e15e1 authored by Pieterjan Vanhoof's avatar Pieterjan Vanhoof Committed by GitHub

Merge pull request #360 from stratisproject/ui

Add mnemonic verification, switch to blue logo everywhere 
parents b899324f 7e47c675
...@@ -33,9 +33,9 @@ let mainWindow = null; ...@@ -33,9 +33,9 @@ let mainWindow = null;
function createWindow() { function createWindow() {
let applicationIcon let applicationIcon
if (serve) { if (serve) {
applicationIcon = nativeImage.createFromPath("./src/assets/images/breeze-logo.png") applicationIcon = nativeImage.createFromPath("./src/assets/images/breeze-logo-tray.png")
} else { } else {
applicationIcon = nativeImage.createFromPath(path.join(__dirname + '/assets/images/breeze-logo.png')) applicationIcon = nativeImage.createFromPath(path.join(__dirname + '/assets/images/breeze-logo-tray.png'))
} }
// Create the browser window. // Create the browser window.
...@@ -169,7 +169,7 @@ function startBitcoinApi() { ...@@ -169,7 +169,7 @@ function startBitcoinApi() {
detached: true detached: true
}); });
} }
bitcoinProcess.stdout.on('data', (data) => { bitcoinProcess.stdout.on('data', (data) => {
writeLog(`Bitcoin: ${data}`); writeLog(`Bitcoin: ${data}`);
...@@ -195,7 +195,7 @@ function startStratisApi() { ...@@ -195,7 +195,7 @@ function startStratisApi() {
detached: true detached: true
}); });
} }
stratisProcess.stdout.on('data', (data) => { stratisProcess.stdout.on('data', (data) => {
writeLog(`Stratis: ${data}`); writeLog(`Stratis: ${data}`);
......
<section id="breeze">
<div class="login d-flex align-items-center text-center">
<div class="container">
<p class="textback text-left mb-5 col-11 mx-auto">
<a (click)="onBackClicked()">
<span class="ico-arrow-left2"></span>
</a>
</p>
<h3 class="display-5">Confirm secret words</h3>
<p class="lead">Please enter the corresponding secret words below.</p>
<!-- /row-->
<div class="row d-flex justify-content-center">
<form class="col-6 mx-auto text-left" [formGroup]="mnemonicForm">
<div class="form-group">
<label class="col-12 row" for="word1">Word n°4</label>
<input type="text" class="form-control" formControlName="word1" id="word1">
<div *ngIf="formErrors.word1" class="text-danger mt-2">{{ formErrors.word1 }}</div>
</div>
<div class="form-group">
<label class="col-12 row" for="word2">Word n°8</label>
<input type="text" class="form-control" formControlName="word2" id="word2">
<div *ngIf="formErrors.word2" class="text-danger mt-2">{{ formErrors.word2 }}</div>
</div>
<div class="form-group">
<label class="col-12 row" for="word3">Word n°12</label>
<input type="text" class="form-control" formControlName="word3" id="word3">
<div *ngIf="formErrors.word3" class="text-danger mt-2">{{ formErrors.word3 }}</div>
</div>
<div *ngIf="matchError" class="text-danger mt-2">{{ matchError }}</div>
</form>
</div>
<div class="row d-flex justify-content-center" *ngIf="!isCreating">
<button type="button" class="btn btn-darkgray btn-lg" [disabled]="!mnemonicForm.valid || isCreating" (click)="onConfirmClicked()">Confirm</button>
</div>
<div class="row d-flex justify-content-center" *ngIf="isCreating">
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
</div>
<!-- /row-->
</div>
<!-- /container-->
</div>
<!-- /login-->
</section>
<!-- /breeze-->
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmMnemonicComponent } from './confirm-mnemonic.component';
describe('ConfirmMnemonicComponent', () => {
let component: ConfirmMnemonicComponent;
let fixture: ComponentFixture<ConfirmMnemonicComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ConfirmMnemonicComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmMnemonicComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { GlobalService } from '../../../shared/services/global.service';
import { ApiService } from '../../../shared/services/api.service';
import { ModalService } from '../../../shared/services/modal.service';
import { WalletCreation } from '../../../shared/classes/wallet-creation';
import { Subscription } from 'rxjs/Subscription';
import { Subscribable } from 'rxjs/Observable';
@Component({
selector: 'app-confirm-mnemonic',
templateUrl: './confirm-mnemonic.component.html',
styleUrls: ['./confirm-mnemonic.component.css']
})
export class ConfirmMnemonicComponent implements OnInit {
constructor(private globalService: GlobalService, private apiService: ApiService, private genericModalService: ModalService, private route: ActivatedRoute, private router: Router, private fb: FormBuilder) {
this.buildMnemonicForm();
}
private subscription: Subscription;
private newWallet: WalletCreation;
public mnemonicForm: FormGroup;
public matchError: string = "";
public isCreating: boolean;
ngOnInit() {
this.subscription = this.route.queryParams.subscribe(params => {
this.newWallet = new WalletCreation(
params["name"],
params["mnemonic"],
params["password"]
)
});
}
private buildMnemonicForm(): void {
this.mnemonicForm = this.fb.group({
"word1": ["",
Validators.compose([
Validators.required,
Validators.minLength(1),
Validators.maxLength(24),
Validators.pattern(/^[a-zA-Z]*$/)
])
],
"word2": ["",
Validators.compose([
Validators.required,
Validators.minLength(1),
Validators.maxLength(24),
Validators.pattern(/^[a-zA-Z]*$/)
])
],
"word3": ["",
Validators.compose([
Validators.required,
Validators.minLength(1),
Validators.maxLength(24),
Validators.pattern(/^[a-zA-Z]*$/)
])
]
});
this.mnemonicForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged();
}
onValueChanged(data?: any) {
if (!this.mnemonicForm) { return; }
const form = this.mnemonicForm;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
this.matchError = "";
}
formErrors = {
'word1': '',
'word2': '',
'word3': ''
};
validationMessages = {
'word1': {
'required': 'This secret word is required.',
'minlength': 'A secret word must be at least one character long',
'maxlength': 'A secret word can not be longer than 24 characters',
'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.'
},
'word2': {
'required': 'This secret word is required.',
'minlength': 'A secret word must be at least one character long',
'maxlength': 'A secret word can not be longer than 24 characters',
'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.'
},
'word3': {
'required': 'This secret word is required.',
'minlength': 'A secret word must be at least one character long',
'maxlength': 'A secret word can not be longer than 24 characters',
'pattern': 'Please enter a valid scret word. [a-Z] are the only characters allowed.'
}
};
public onConfirmClicked() {
this.checkMnemonic();
if (this.checkMnemonic()) {
this.isCreating = true;
this.createWallets(this.newWallet);
}
}
public onBackClicked() {
this.router.navigate(['/setup/create/show-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password }});
}
private checkMnemonic(): boolean {
let mnemonic = this.newWallet.mnemonic;
let mnemonicArray = mnemonic.split(" ");
if (this.mnemonicForm.get("word1").value.trim() === mnemonicArray[3] && this.mnemonicForm.get("word2").value.trim() === mnemonicArray[7] && this.mnemonicForm.get("word3").value.trim() === mnemonicArray[11]) {
return true;
} else {
this.matchError = "The secret words do not match."
return false;
}
}
private createWallets(wallet: WalletCreation) {
this.apiService
.createBitcoinWallet(wallet)
.subscribe(
response => {
if (response.status >= 200 && response.status < 400){
// Bitcoin wallet created
}
},
error => {
console.log(error);
this.isCreating = false;
if (error.status === 0) {
this.genericModalService.openModal(null, null);
} else if (error.status >= 400) {
if (!error.json().errors[0]) {
console.log(error);
}
else {
this.genericModalService.openModal(null, error.json().errors[0].message);
this.router.navigate(['/setup/create']);
}
}
},
() => this.createStratisWallet(wallet)
)
;
}
private createStratisWallet(wallet: WalletCreation) {
this.apiService
.createStratisWallet(wallet)
.subscribe(
response => {
if (response.status >= 200 && response.status < 400){
this.genericModalService.openModal("Wallet Created", "Your wallet has been created.<br>Keep your secret words and password safe!");
this.router.navigate(['']);
}
},
error => {
this.isCreating = false;
console.log(error);
if (error.status === 0) {
this.genericModalService.openModal(null, null);
} else if (error.status >= 400) {
if (!error.json().errors[0]) {
console.log(error);
}
else {
this.genericModalService.openModal(null, error.json().errors[0].message);
this.router.navigate(['/setup/create']);
}
}
}
)
;
}
}
...@@ -31,14 +31,8 @@ ...@@ -31,14 +31,8 @@
</form> </form>
</div> </div>
<!-- /row--> <!-- /row-->
<div class="row d-flex justify-content-center" *ngIf="!isCreating"> <div class="row d-flex justify-content-center">
<button type="button" class="btn btn-darkgray btn-lg" [disabled]="!createWalletForm.valid || isCreating" (click)="onCreateClicked()">Create</button> <button type="button" class="btn btn-darkgray btn-lg" [disabled]="!createWalletForm.valid" (click)="onContinueClicked()">Continue</button>
</div>
<div class="row d-flex justify-content-center" *ngIf="isCreating">
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
</div> </div>
<!-- /row--> <!-- /row-->
</div> </div>
......
...@@ -25,7 +25,6 @@ export class CreateComponent implements OnInit { ...@@ -25,7 +25,6 @@ export class CreateComponent implements OnInit {
public createWalletForm: FormGroup; public createWalletForm: FormGroup;
private newWallet: WalletCreation; private newWallet: WalletCreation;
private mnemonic: string; private mnemonic: string;
public isCreating: boolean = false;
ngOnInit() { ngOnInit() {
this.getNewMnemonic(); this.getNewMnemonic();
...@@ -101,15 +100,15 @@ export class CreateComponent implements OnInit { ...@@ -101,15 +100,15 @@ export class CreateComponent implements OnInit {
this.router.navigate(["/setup"]); this.router.navigate(["/setup"]);
} }
public onCreateClicked() { public onContinueClicked() {
this.isCreating = true;
if (this.mnemonic) { if (this.mnemonic) {
this.newWallet = new WalletCreation( this.newWallet = new WalletCreation(
this.createWalletForm.get("walletName").value, this.createWalletForm.get("walletName").value,
this.mnemonic, this.mnemonic,
this.createWalletForm.get("walletPassword").value, this.createWalletForm.get("walletPassword").value,
); );
this.createWallets(this.newWallet); this.router.navigate(['/setup/create/show-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password }});
//this.createWallets(this.newWallet);
} }
} }
...@@ -138,61 +137,4 @@ export class CreateComponent implements OnInit { ...@@ -138,61 +137,4 @@ export class CreateComponent implements OnInit {
) )
; ;
} }
private createWallets(wallet: WalletCreation) {
this.apiService
.createBitcoinWallet(wallet)
.subscribe(
response => {
if (response.status >= 200 && response.status < 400){
// Bitcoin wallet created
}
},
error => {
console.log(error);
this.isCreating = false;
if (error.status === 0) {
this.genericModalService.openModal(null, null);
} else if (error.status >= 400) {
if (!error.json().errors[0]) {
console.log(error);
}
else {
this.genericModalService.openModal(null, error.json().errors[0].message);
}
}
},
() => this.createStratisWallet(wallet)
)
;
}
private createStratisWallet(wallet: WalletCreation) {
this.apiService
.createStratisWallet(wallet)
.subscribe(
response => {
if (response.status >= 200 && response.status < 400){
let walletBody = "Your wallet has been created.<br><br>Please write down your 12 word passphrase: <br>" + this.mnemonic + "<br><br>You can recover your wallet on any computer with:<br>- your passphrase<br>- your password<br>- In addition, knowing the approximate date at which you created your wallet will speed up recovery.<br><br>Unlike most other wallets if an attacker acquires your passphrase, he will not be able to hack your wallet without knowing your password. On the contrary, unlike other wallets, you will not be able to recover your wallet only with your passphrase if you lose your password.";
this.genericModalService.openModal("Wallet Info", walletBody);
this.router.navigate(['']);
}
},
error => {
this.isCreating = false;
console.log(error);
if (error.status === 0) {
this.genericModalService.openModal(null, null);
} else if (error.status >= 400) {
if (!error.json().errors[0]) {
console.log(error);
}
else {
this.genericModalService.openModal(null, error.json().errors[0].message);
}
}
}
)
;
}
} }
<section id="breeze">
<div class="login d-flex align-items-center text-center">
<div class="container">
<p class="textback text-left col-11 mx-auto">
<a (click)="onBackClicked()">
<span class="ico-arrow-left2"></span>
</a>
</p>
<h3 class="display-5">Secret words</h3>
<p class="lead">Please write down your secret words and password. <br>You will need <strong>both</strong> to recover your wallet in the future.</p>
<div class="row d-flex justify-content-center mt-4">
<ul class="list-inline list-unstyled col-6 mx-auto">
<li *ngFor="let word of mnemonicArray; let i=index" class="list-inline-item">
<span class="badge badge-default">{{ i + 1 }}. {{ word }}</span>
</li>
</ul>
</div>
<div class="form-control-feedback text-center my-3">Write down your secret words and password before you continue</div>
<!-- /row-->
<div class="row d-flex justify-content-center">
<!-- <button type="button" class="btn btn-linkgray btn-lg col-12">Save the list somewhere</button> -->
<button type="button" class="btn btn-darkgray btn-lg" (click)="onContinueClicked()">Continue</button>
</div>
<!-- /row-->
</div>
<!-- /container-->
</div>
<!-- /login-->
</section>
<!-- /breeze-->
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ShowMnemonicComponent } from './show-mnemonic.component';
describe('ShowMnemonicComponent', () => {
let component: ShowMnemonicComponent;
let fixture: ComponentFixture<ShowMnemonicComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShowMnemonicComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShowMnemonicComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { WalletCreation } from '../../../shared/classes/wallet-creation';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'app-show-mnemonic',
templateUrl: './show-mnemonic.component.html',
styleUrls: ['./show-mnemonic.component.css']
})
export class ShowMnemonicComponent implements OnInit, OnDestroy {
constructor(private route: ActivatedRoute, private router: Router) { }
private parameters: any;
private mnemonic: string;
private subscription: Subscription;
private newWallet: WalletCreation;
public mnemonicArray: string[];
ngOnInit() {
this.subscription = this.route.queryParams.subscribe(params => {
this.newWallet = new WalletCreation(
params["name"],
params["mnemonic"],
params["password"]
)
});
this.showMnemonic();
}
private showMnemonic() {
this.mnemonic = this.newWallet.mnemonic;
this.mnemonicArray = this.mnemonic.split(" ");
}
public onContinueClicked() {
this.router.navigate(['/setup/create/confirm-mnemonic'], { queryParams : { name: this.newWallet.name, mnemonic: this.newWallet.mnemonic, password: this.newWallet.password }});
}
public onBackClicked() {
this.router.navigate(['/setup/create']);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
...@@ -3,12 +3,16 @@ import { RouterModule, Routes } from '@angular/router'; ...@@ -3,12 +3,16 @@ import { RouterModule, Routes } from '@angular/router';
import { SetupComponent } from './setup.component'; import { SetupComponent } from './setup.component';
import { CreateComponent } from './create/create.component'; import { CreateComponent } from './create/create.component';
import { ShowMnemonicComponent } from './create/show-mnemonic/show-mnemonic.component';
import { ConfirmMnemonicComponent } from './create/confirm-mnemonic/confirm-mnemonic.component';
import { RecoverComponent } from './recover/recover.component'; import { RecoverComponent } from './recover/recover.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', redirectTo: 'setup', pathMatch: 'full'}, { path: '', redirectTo: 'setup', pathMatch: 'full'},
{ path: 'setup', component: SetupComponent }, { path: 'setup', component: SetupComponent },
{ path: 'create', component: CreateComponent }, { path: 'create', component: CreateComponent },
{ path: 'create/show-mnemonic', component: ShowMnemonicComponent },
{ path: 'create/confirm-mnemonic', component: ConfirmMnemonicComponent },
{ path: 'recover', component: RecoverComponent } { path: 'recover', component: RecoverComponent }
]; ];
......
...@@ -10,6 +10,8 @@ import { SharedModule } from '../shared/shared.module'; ...@@ -10,6 +10,8 @@ import { SharedModule } from '../shared/shared.module';
import { SetupRoutingModule } from './setup-routing.module'; import { SetupRoutingModule } from './setup-routing.module';
import { RecoverComponent } from './recover/recover.component'; import { RecoverComponent } from './recover/recover.component';
import { ShowMnemonicComponent } from './create/show-mnemonic/show-mnemonic.component';
import { ConfirmMnemonicComponent } from './create/confirm-mnemonic/confirm-mnemonic.component';
@NgModule({ @NgModule({
imports: [ imports: [
...@@ -22,7 +24,9 @@ import { RecoverComponent } from './recover/recover.component'; ...@@ -22,7 +24,9 @@ import { RecoverComponent } from './recover/recover.component';
declarations: [ declarations: [
CreateComponent, CreateComponent,
SetupComponent, SetupComponent,
RecoverComponent RecoverComponent,
ShowMnemonicComponent,
ConfirmMnemonicComponent
], ],
exports: [], exports: [],
providers: [] providers: []
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<label ngbButtonLabel class="btn btn-outline-danger"> <label ngbButtonLabel class="btn btn-outline-danger">
<input ngbButton type="radio" value="low">Low/Slow <input ngbButton type="radio" value="low">Low/Slow
</label> </label>
<label ngbButtonLabel class="btn btn-outline-warning"> <label ngbButtonLabel class="btn btn-outline-warning mx-2">
<input ngbButton type="radio" value="medium">Medium <input ngbButton type="radio" value="medium">Medium
</label> </label>
<label ngbButtonLabel class="btn btn-outline-success"> <label ngbButtonLabel class="btn btn-outline-success">
......
...@@ -169,7 +169,7 @@ export class SendComponent implements OnInit { ...@@ -169,7 +169,7 @@ export class SendComponent implements OnInit {
} }
}, },
() => { () => {
this.estimatedFee = this.responseMessage.fee; this.estimatedFee = this.responseMessage;
} }
) )
; ;
......
...@@ -8,4 +8,8 @@ if (environment.production) { ...@@ -8,4 +8,8 @@ if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic()
.bootstrapModule(AppModule, {
preserveWhitespaces: false
})
.catch(err => console.error(err));
...@@ -10,10 +10,6 @@ code {color: $spacegray !important;} ...@@ -10,10 +10,6 @@ code {color: $spacegray !important;}
.form-control:-moz-placeholder { color: $input-color-placeholder; } /* Mozilla Firefox 4 to 18 */ .form-control:-moz-placeholder { color: $input-color-placeholder; } /* Mozilla Firefox 4 to 18 */
.form-control:-ms-input-placeholder { color: $input-color-placeholder; } /* Internet Explorer 10-11 */ .form-control:-ms-input-placeholder { color: $input-color-placeholder; } /* Internet Explorer 10-11 */
// badge default
.badge {
&.badge-default {color: $gray;}
}
// feedback // feedback
.form-control-feedback { .form-control-feedback {
color: $pink; color: $pink;
......
...@@ -774,7 +774,7 @@ $popover-arrow-outer-color: fade-in($popover-border-color, .05) !defau ...@@ -774,7 +774,7 @@ $popover-arrow-outer-color: fade-in($popover-border-color, .05) !defau
// Badges // Badges
$badge-default-bg: rgba(152,163,179,.15) !default; $badge-default-bg: $white !default;
$badge-primary-bg: $brand-primary !default; $badge-primary-bg: $brand-primary !default;
$badge-success-bg: rgba(46,204,169,.2) !default; $badge-success-bg: rgba(46,204,169,.2) !default;
$badge-info-bg: $brand-info !default; $badge-info-bg: $brand-info !default;
......
...@@ -167,3 +167,11 @@ $btn-primary-color: $white !important; ...@@ -167,3 +167,11 @@ $btn-primary-color: $white !important;
a {color: $link-color !important;} a {color: $link-color !important;}
} }
} }
// SETUP
.badge {
&.badge-default {
color: $gray;
background-color: $white !important;
}
}
...@@ -31,10 +31,6 @@ code { ...@@ -31,10 +31,6 @@ code {
} }
/* Internet Explorer 10-11 */ /* Internet Explorer 10-11 */
.badge.badge-default {
color: #98A3B3;
}
.form-control-feedback { .form-control-feedback {
color: #FF5B8A; color: #FF5B8A;
margin: .5em 0; margin: .5em 0;
...@@ -5319,11 +5315,11 @@ a.badge:focus, a.badge:hover { ...@@ -5319,11 +5315,11 @@ a.badge:focus, a.badge:hover {
} }
.badge-default { .badge-default {
background-color: rgba(152, 163, 179, 0.15); background-color: #fff;
} }
.badge-default[href]:focus, .badge-default[href]:hover { .badge-default[href]:focus, .badge-default[href]:hover {
background-color: rgba(123, 137, 157, 0.15); background-color: #e6e6e6;
} }
.badge-primary { .badge-primary {
...@@ -9945,4 +9941,9 @@ a.text-gray-dark:focus, a.text-gray-dark:hover { ...@@ -9945,4 +9941,9 @@ a.text-gray-dark:focus, a.text-gray-dark:hover {
color: #148DC7 !important; color: #148DC7 !important;
} }
.badge.badge-default {
color: #98A3B3;
background-color: #fff !important;
}
/*# sourceMappingURL=styles.css.map */ /*# sourceMappingURL=styles.css.map */
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment