Commit 7926049a authored by Pieterjan Vanhoof's avatar Pieterjan Vanhoof Committed by GitHub

Merge pull request #162 from stratisproject/ui

Add password confirmation box, add password strength validation, add send confirmation modal
parents 2579d21e 5c1388d3
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
"cross-env": "5.0.1", "cross-env": "5.0.1",
"css-loader": "^0.28.4", "css-loader": "^0.28.4",
"cssnano": "^3.10.0", "cssnano": "^3.10.0",
"electron": "^1.6.11", "electron": "^1.7.4",
"electron-packager": "^8.7.2", "electron-packager": "^8.7.2",
"electron-reload": "1.2.1", "electron-reload": "1.2.1",
"exports-loader": "^0.6.4", "exports-loader": "^0.6.4",
......
...@@ -17,6 +17,7 @@ import { ApiService } from './shared/services/api.service'; ...@@ -17,6 +17,7 @@ import { ApiService } from './shared/services/api.service';
import { GlobalService } from './shared/services/global.service'; import { GlobalService } from './shared/services/global.service';
import { SendComponent } from './wallet/send/send.component'; import { SendComponent } from './wallet/send/send.component';
import { SendConfirmationComponent } from './wallet/send/send-confirmation/send-confirmation.component';
import { ReceiveComponent } from './wallet/receive/receive.component'; import { ReceiveComponent } from './wallet/receive/receive.component';
import { TransactionDetailsComponent } from './wallet/transaction-details/transaction-details.component'; import { TransactionDetailsComponent } from './wallet/transaction-details/transaction-details.component';
import { LogoutConfirmationComponent } from './wallet/logout-confirmation/logout-confirmation.component'; import { LogoutConfirmationComponent } from './wallet/logout-confirmation/logout-confirmation.component';
...@@ -39,11 +40,13 @@ import { LogoutConfirmationComponent } from './wallet/logout-confirmation/logout ...@@ -39,11 +40,13 @@ import { LogoutConfirmationComponent } from './wallet/logout-confirmation/logout
LoginComponent, LoginComponent,
LogoutConfirmationComponent, LogoutConfirmationComponent,
SendComponent, SendComponent,
SendConfirmationComponent,
ReceiveComponent, ReceiveComponent,
TransactionDetailsComponent TransactionDetailsComponent
], ],
entryComponents: [ entryComponents: [
SendComponent, SendComponent,
SendConfirmationComponent,
ReceiveComponent, ReceiveComponent,
TransactionDetailsComponent, TransactionDetailsComponent,
LogoutConfirmationComponent LogoutConfirmationComponent
......
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
<input type="password" class="form-control form-control-success" formControlName="walletPassword" id="walletPassword" placeholder="Enter a password."> <input type="password" class="form-control form-control-success" formControlName="walletPassword" id="walletPassword" placeholder="Enter a password.">
<div *ngIf="formErrors.walletPassword" class="form-control-feedback">{{ formErrors.walletPassword }}</div> <div *ngIf="formErrors.walletPassword" class="form-control-feedback">{{ formErrors.walletPassword }}</div>
</div> </div>
<div class="form-group">
<label class="text-left" for="walletPasswordConfirmation">Confirm Password</label>
<input type="password" class="form-control form-control-success" formControlName="walletPasswordConfirmation" id="walletPasswordConfirmation" placeholder="Enter a password.">
<div *ngIf="formErrors.walletPasswordConfirmation" class="form-control-feedback">{{ formErrors.walletPasswordConfirmation }}</div>
<div class="form-control-feedback">Your password will be required to recover your wallet in the future. Keep it safe.</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="text-left" for="walletNetwork">Network</label> <label class="text-left" for="walletNetwork">Network</label>
<select class="form-control custom-select" name="network" formControlName="selectNetwork"> <select class="form-control custom-select" name="network" formControlName="selectNetwork">
......
...@@ -5,6 +5,8 @@ import { Router } from '@angular/router'; ...@@ -5,6 +5,8 @@ import { Router } from '@angular/router';
import { GlobalService } from '../../shared/services/global.service'; import { GlobalService } from '../../shared/services/global.service';
import { ApiService } from '../../shared/services/api.service'; import { ApiService } from '../../shared/services/api.service';
import { PasswordValidationDirective } from '../../shared/directives/password-validation.directive';
import { WalletCreation } from '../../shared/classes/wallet-creation'; import { WalletCreation } from '../../shared/classes/wallet-creation';
import { Mnemonic } from '../../shared/classes/mnemonic'; import { Mnemonic } from '../../shared/classes/mnemonic';
...@@ -25,14 +27,23 @@ export class CreateComponent { ...@@ -25,14 +27,23 @@ export class CreateComponent {
private buildCreateForm(): void { private buildCreateForm(): void {
this.createWalletForm = this.fb.group({ this.createWalletForm = this.fb.group({
"walletName": ["", [ "walletName": ["",
Validators.compose([
Validators.required, Validators.required,
Validators.minLength(3), Validators.minLength(3),
Validators.maxLength(24) Validators.maxLength(24),
] Validators.pattern(/^[a-zA-Z0-9]*$/)
])
], ],
"walletPassword": ["", Validators.required], "walletPassword": ["",
Validators.compose([
Validators.required,
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{10,})/)])
],
"walletPasswordConfirmation": ["", Validators.required],
"selectNetwork": ["test", Validators.required] "selectNetwork": ["test", Validators.required]
}, {
validator: PasswordValidationDirective.MatchPassword
}); });
this.createWalletForm.valueChanges this.createWalletForm.valueChanges
...@@ -58,17 +69,24 @@ export class CreateComponent { ...@@ -58,17 +69,24 @@ export class CreateComponent {
formErrors = { formErrors = {
'walletName': '', 'walletName': '',
'walletPassword': '' 'walletPassword': '',
'walletPasswordConfirmation': ''
}; };
validationMessages = { validationMessages = {
'walletName': { 'walletName': {
'required': 'Name is required.', 'required': 'Name is required.',
'minlength': 'Name must be at least 3 characters long.', 'minlength': 'Name must be at least 3 characters long.',
'maxlength': 'Name cannot be more than 24 characters long.' 'maxlength': 'Name cannot be more than 24 characters long.',
'pattern': 'Enter a valid wallet name. [a-Z] and [0-9] are the only characters allowed.'
}, },
'walletPassword': { 'walletPassword': {
'required': 'A password is required.' 'required': 'A password is required.',
'pattern': 'A password must be at least 10 characters long and contain one lowercase and uppercase alphabetical character and a number.'
},
'walletPasswordConfirmation': {
'required': 'Confirm your password.',
'walletPasswordConfirmation': 'Passwords do not match.'
} }
}; };
......
import { PasswordValidationDirective } from './password-validation.directive';
describe('PasswordValidationDirective', () => {
it('should create an instance', () => {
const directive = new PasswordValidationDirective();
expect(directive).toBeTruthy();
});
});
import { Directive } from '@angular/core';
import { AbstractControl } from '@angular/forms';
@Directive({
selector: '[appPasswordValidation]'
})
export class PasswordValidationDirective {
constructor() { }
static MatchPassword(AC: AbstractControl) {
let password = AC.get('walletPassword').value;
let confirmPassword = AC.get('walletPasswordConfirmation').value;
if(password != confirmPassword) {
AC.get('walletPasswordConfirmation').setErrors( { walletPasswordConfirmation: true } )
} else {
return null
}
}
}
\ No newline at end of file
...@@ -2,10 +2,11 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; ...@@ -2,10 +2,11 @@ import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CoinNotationPipe } from './pipes/coin-notation.pipe'; import { CoinNotationPipe } from './pipes/coin-notation.pipe';
import { AutoFocusDirective } from './directives/auto-focus.directive'; import { AutoFocusDirective } from './directives/auto-focus.directive';
import { PasswordValidationDirective } from './directives/password-validation.directive';
@NgModule({ @NgModule({
imports: [CommonModule], imports: [CommonModule],
declarations: [CoinNotationPipe, AutoFocusDirective], declarations: [CoinNotationPipe, AutoFocusDirective, PasswordValidationDirective],
exports: [CoinNotationPipe, AutoFocusDirective] exports: [CoinNotationPipe, AutoFocusDirective]
}) })
......
<div id="modalCheck" tabindex="-1" role="dialog" aria-labelledby="modal_check" aria-hidden="true">
<div class="modal-dialog" role="document">
<div>
<div class="modal-body">
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none"/><path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/></svg>
<p class="lead text-success text-center">
<strong>Your transaction has been sent with success !</strong>
<small class="text-center">
<a class="btn btn-link" (click)="toggleDetails()">Show details</a>
</small>
</p>
<div *ngIf="showDetails" id="collapseExample">
<div class="card card-block">
<ul class="list-inline row">
<li class="list-inline-item col blockLabel">Type</li>
<li class="list-inline-item col-9 blockText">Sent</li>
</ul>
<ul class="list-inline row">
<li class="list-inline-item col blockLabel">Amount</li>
<li class="list-inline-item col-9 blockText"><strong class="text-danger">-{{ transaction.amount }}</strong> <small class="text-uppercase ml-2">BTC</small></li>
</ul>
<ul class="list-inline row">
<li class="list-inline-item col blockLabel">Destination</li>
<li class="list-inline-item col-9 blockText"><code>{{ transaction.destinationAddress }}</code></li>
</ul>
<!-- <ul class="list-inline row">
<li class="list-inline-item col blockLabel">Date</li>
<li class="list-inline-item col-9 blockText"><small>{{ transaction.timestamp * 1000 | date:'medium' }}</small></li>
</ul>
<ul class="list-inline row">
<li class="list-inline-item col blockLabel">transaction ID</li>
<li class="list-inline-item col-9 blockText blockID"><code>{{ transaction.id }}</code></li>
</ul> -->
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-lg btn-primary" data-dismiss="modal" aria-label="Close" (click)="activeModal.close('Close click')">OK</button>
</div>
</div>
</div>
</div>
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SendConfirmationComponent } from './send-confirmation.component';
describe('SendConfirmationComponent', () => {
let component: SendConfirmationComponent;
let fixture: ComponentFixture<SendConfirmationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SendConfirmationComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SendConfirmationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Input } from '@angular/core';
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-send-confirmation',
templateUrl: './send-confirmation.component.html',
styleUrls: ['./send-confirmation.component.css']
})
export class SendConfirmationComponent implements OnInit {
@Input() transaction: any;
constructor(public activeModal: NgbActiveModal) { }
private showDetails: boolean = false;
ngOnInit() {
console.log(this.transaction);
}
toggleDetails() {
this.showDetails = !this.showDetails;
}
}
...@@ -3,11 +3,13 @@ import { ApiService } from '../../shared/services/api.service'; ...@@ -3,11 +3,13 @@ import { ApiService } from '../../shared/services/api.service';
import { GlobalService } from '../../shared/services/global.service'; import { GlobalService } from '../../shared/services/global.service';
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
import {NgbModal, NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { TransactionBuilding } from '../../shared/classes/transaction-building'; import { TransactionBuilding } from '../../shared/classes/transaction-building';
import { TransactionSending } from '../../shared/classes/transaction-sending'; import { TransactionSending } from '../../shared/classes/transaction-sending';
import { SendConfirmationComponent } from './send-confirmation/send-confirmation.component';
@Component({ @Component({
selector: 'send-component', selector: 'send-component',
templateUrl: './send.component.html', templateUrl: './send.component.html',
...@@ -15,13 +17,14 @@ import { TransactionSending } from '../../shared/classes/transaction-sending'; ...@@ -15,13 +17,14 @@ import { TransactionSending } from '../../shared/classes/transaction-sending';
}) })
export class SendComponent { export class SendComponent {
constructor(private apiService: ApiService, private globalService: GlobalService, public activeModal: NgbActiveModal, private fb: FormBuilder) { constructor(private apiService: ApiService, private globalService: GlobalService, private modalService: NgbModal, public activeModal: NgbActiveModal, private fb: FormBuilder) {
this.buildSendForm(); this.buildSendForm();
} }
private sendForm: FormGroup; private sendForm: FormGroup;
private responseMessage: any; private responseMessage: any;
private errorMessage: string; private errorMessage: string;
private transaction: TransactionBuilding;
private buildSendForm(): void { private buildSendForm(): void {
this.sendForm = this.fb.group({ this.sendForm = this.fb.group({
...@@ -76,8 +79,7 @@ export class SendComponent { ...@@ -76,8 +79,7 @@ export class SendComponent {
}; };
private send() { private send() {
this.transaction = new TransactionBuilding(
let transaction = new TransactionBuilding(
this.globalService.getWalletName(), this.globalService.getWalletName(),
this.globalService.getCoinType(), this.globalService.getCoinType(),
"account 0", "account 0",
...@@ -89,7 +91,7 @@ export class SendComponent { ...@@ -89,7 +91,7 @@ export class SendComponent {
); );
this.apiService this.apiService
.buildTransaction(transaction) .buildTransaction(this.transaction)
.subscribe( .subscribe(
response => { response => {
if (response.status >= 200 && response.status < 400){ if (response.status >= 200 && response.status < 400){
...@@ -110,7 +112,7 @@ export class SendComponent { ...@@ -110,7 +112,7 @@ export class SendComponent {
} }
} }
}, },
() => this.sendTransaction("123") () => this.sendTransaction(this.responseMessage.hex)
) )
; ;
}; };
...@@ -150,8 +152,14 @@ export class SendComponent { ...@@ -150,8 +152,14 @@ export class SendComponent {
alert(error.json().errors[0].message); alert(error.json().errors[0].message);
} }
} }
} },
()=>this.openConfirmationModal()
) )
; ;
} }
private openConfirmationModal() {
const modalRef = this.modalService.open(SendConfirmationComponent);
modalRef.componentInstance.transaction = this.transaction;
}
} }
...@@ -7,7 +7,7 @@ import { MenuComponent } from './menu/menu.component'; ...@@ -7,7 +7,7 @@ import { MenuComponent } from './menu/menu.component';
import { DashboardComponent } from './dashboard/dashboard.component'; import { DashboardComponent } from './dashboard/dashboard.component';
import { HistoryComponent } from './history/history.component'; import { HistoryComponent } from './history/history.component';
import {SharedModule} from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { WalletRoutingModule } from './wallet-routing.module'; import { WalletRoutingModule } from './wallet-routing.module';
import { SidebarComponent } from './sidebar/sidebar.component'; import { SidebarComponent } from './sidebar/sidebar.component';
import { StatusBarComponent } from './status-bar/status-bar.component'; import { StatusBarComponent } from './status-bar/status-bar.component';
......
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