1
0
Fork 0
forked from lthn/blockchain

all three tasks have been completed (#253)

* all three tasks have been completed

* created copy seed phrase
This commit is contained in:
Nazar 2020-11-30 23:03:25 +02:00 committed by GitHub
parent 8197265da1
commit 1d7bd3bda7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2153 additions and 346 deletions

View file

@ -120,13 +120,16 @@
"BUTTON_CREATE": "Create wallet",
"NOT_CORRECT_FILE_OR_PASSWORD": "Invalid wallet file or password does not match",
"CHOOSE_PATH": "Please choose a path",
"SEED_PASSWORD": "Seed password",
"OK": "OK",
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_DUPLICATE": "Name is duplicate",
"MAX_LENGTH": "Maximum name length reached",
"CONFIRM_NOT_MATCH": "Confirm password not match",
"KEY_REQUIRED": "Key is required",
"KEY_NOT_VALID": "Key not valid"
"KEY_NOT_VALID": "Key not valid",
"INCORRECT_PASSWORD": "Incorrect password"
}
},
"SEED_PHRASE": {
@ -214,10 +217,21 @@
"SEED_PHRASE_HINT": "Click to reveal the seed phrase",
"BUTTON_SAVE": "Save",
"BUTTON_REMOVE": "Close wallet",
"CREATE_PASSWORD_SECURE": "Create a password to secure your seed",
"INFO": "info",
"SEED_IS_UNSECURED": "Seed is unsecured",
"SEED_IS_SECURED": "Seed is secured",
"REMEMBER_YOU_WILL_REQUIRE": "Remember, you will require the password to restore it.",
"FORM": {
"CONFIRM_PASSWORD": "Confirm password",
"GENERATE_SECURE_SEED": "Generate Secure Seed",
"SECURED_SEED_WILL_REQUIRE": "Secured seed will require this password to restore."
},
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_DUPLICATE": "Name is duplicate",
"MAX_LENGTH": "Maximum name length reached"
"MAX_LENGTH": "Maximum name length reached",
"PASSWORDS_DONT_MATCH": "Passwords don't match"
}
},
"ASSIGN_ALIAS": {

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 384"><defs><style>.cls-1{fill:#fff;}</style></defs><title>info</title><path class="cls-1" d="M192,42.05476A149.94522,149.94522,0,0,1,298.02728,298.02728,149.94522,149.94522,0,1,1,85.97271,85.97271,148.96374,148.96374,0,0,1,192,42.05476M192,0C85.96132,0,0,85.96132,0,192S85.96132,384,192,384s192-85.96132,192-192S298.03868,0,192,0Z"/><rect class="cls-1" x="171" y="247.94524" width="42" height="42"/><rect class="cls-1" x="171" y="94.05476" width="42" height="120"/></svg>

After

Width:  |  Height:  |  Size: 561 B

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 320 384" style="enable-background:new 0 0 320 384;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_2_);}
.st1{clip-path:url(#SVGID_2_);fill:none;stroke:#000000;stroke-width:42;stroke-miterlimit:10;}
</style>
<g>
<defs>
<rect id="SVGID_1_" width="320" height="384"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<path class="st0" d="M159.9,44.5L278,85.8V232c0,0.9-0.4,9.2-14.7,26c-11,13-26.9,27.1-47.3,42c-20.6,15.1-41.6,27.8-56.1,36.1
c-14.5-8.3-35.4-21-56-36.1c-20.3-14.9-36.2-29-47.2-42C42.4,241.2,42,232.9,42,232V85.8L159.9,44.5 M159.9,0L0,56v176
c0,70.7,159.9,152,159.9,152S320,302.7,320,232V56L159.9,0z"/>
<polyline class="st1" points="100.1,182.2 142.3,218.5 220.3,145.1 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M26,15v-5c0-5.5-4.5-10-10-10h-0.1c-5.5,0-10,4.5-10,10v5H3v17h12.9H16h13V15H26z M9.9,10c0-3.3,2.7-6,6-6H16
c3.3,0,6,2.7,6,6v5H9.9V10z M14,27v-7h4v7H14z"/>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M22,15h-3.4H9.9v-5c0-3.3,2.7-6,6-6H16c3,0,5.4,2.2,5.9,5h4c-0.5-5-4.8-9-9.9-9h-0.1c-5.5,0-10,4.5-10,10v5H3
v17h12.9H16h13V15h-3H22z M18,27h-4v-7h4V27z"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

View file

@ -205,6 +205,16 @@ button {
color: themed(redTextColor);
}
}
.success-block {
font-size: 1rem;
line-height: 1.4rem;
align-self: flex-end;
text-align: right;
@include themify($themes) {
color: themed(greenTextColor);
}
}
}
.error-text {

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 384"><defs><style>.cls-1{fill:#fff;}</style></defs><title>info</title><path class="cls-1" d="M192,42.05476A149.94522,149.94522,0,0,1,298.02728,298.02728,149.94522,149.94522,0,1,1,85.97271,85.97271,148.96374,148.96374,0,0,1,192,42.05476M192,0C85.96132,0,0,85.96132,0,192S85.96132,384,192,384s192-85.96132,192-192S298.03868,0,192,0Z"/><rect class="cls-1" x="171" y="247.94524" width="42" height="42"/><rect class="cls-1" x="171" y="94.05476" width="42" height="120"/></svg>

After

Width:  |  Height:  |  Size: 561 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -649,7 +649,7 @@ module.exports = function (NAME, wrapper, methods, common, IS_MAP, IS_WEAK) {
/*! no static exports found */
/***/ (function(module, exports) {
var core = module.exports = { version: '2.6.11' };
var core = module.exports = { version: '2.6.12' };
if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef
@ -1823,7 +1823,7 @@ var store = global[SHARED] || (global[SHARED] = {});
})('versions', []).push({
version: core.version,
mode: __webpack_require__(/*! ./_library */ "./node_modules/core-js/modules/_library.js") ? 'pure' : 'global',
copyright: '© 2019 Denis Pushkarev (zloirock.ru)'
copyright: '© 2020 Denis Pushkarev (zloirock.ru)'
});
@ -5800,8 +5800,8 @@ __webpack_require__.r(__webpack_exports__);
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(/*! /Users/mekasan/Projects/Projects/zano_v1/src/gui/qt-daemon/html_source/src/polyfills.ts */"./src/polyfills.ts");
module.exports = __webpack_require__(/*! /Users/mekasan/Projects/Projects/zano_v1/src/gui/qt-daemon/html_source/node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js */"./node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js");
__webpack_require__(/*! D:\Project\WORK_NEW\zano\src\gui\qt-daemon\html_source\src\polyfills.ts */"./src/polyfills.ts");
module.exports = __webpack_require__(/*! D:\Project\WORK_NEW\zano\src\gui\qt-daemon\html_source\node_modules\@angular-devkit\build-angular\src\angular-cli-files\models\jit-polyfills.js */"./node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills.js");
/***/ })

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 320 384" style="enable-background:new 0 0 320 384;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_2_);}
.st1{clip-path:url(#SVGID_2_);fill:none;stroke:#000000;stroke-width:42;stroke-miterlimit:10;}
</style>
<g>
<defs>
<rect id="SVGID_1_" width="320" height="384"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<path class="st0" d="M159.9,44.5L278,85.8V232c0,0.9-0.4,9.2-14.7,26c-11,13-26.9,27.1-47.3,42c-20.6,15.1-41.6,27.8-56.1,36.1
c-14.5-8.3-35.4-21-56-36.1c-20.3-14.9-36.2-29-47.2-42C42.4,241.2,42,232.9,42,232V85.8L159.9,44.5 M159.9,0L0,56v176
c0,70.7,159.9,152,159.9,152S320,302.7,320,232V56L159.9,0z"/>
<polyline class="st1" points="100.1,182.2 142.3,218.5 220.3,145.1 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M26,15v-5c0-5.5-4.5-10-10-10h-0.1c-5.5,0-10,4.5-10,10v5H3v17h12.9H16h13V15H26z M9.9,10c0-3.3,2.7-6,6-6H16
c3.3,0,6,2.7,6,6v5H9.9V10z M14,27v-7h4v7H14z"/>
</svg>

After

Width:  |  Height:  |  Size: 582 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M22,15h-3.4H9.9v-5c0-3.3,2.7-6,6-6H16c3,0,5.4,2.2,5.9,5h4c-0.5-5-4.8-9-9.9-9h-0.1c-5.5,0-10,4.5-10,10v5H3
v17h12.9H16h13V15h-3H22z M18,27h-4v-7h4V27z"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -36,6 +36,7 @@
"ngx-contextmenu": "^5.1.1",
"ngx-papaparse": "^3.0.3",
"qrcode": "^1.3.0",
"node-sass": "^4.0.0",
"rxjs": "~6.3.3",
"zone.js": "~0.8.26"
},

View file

@ -412,23 +412,28 @@ export class BackendService {
this.runCommand('close_wallet', {wallet_id: +wallet_id}, callback);
}
getSmartWalletInfo(wallet_id, callback) {
this.runCommand('get_smart_wallet_info', {wallet_id: +wallet_id}, callback);
getSmartWalletInfo({wallet_id, seed_password}, callback) {
this.runCommand('get_smart_wallet_info', {wallet_id: +wallet_id, seed_password}, callback);
}
getSeedPhraseInfo(param, callback) {
this.runCommand('get_seed_phrase_info', param, callback);
}
runWallet(wallet_id, callback?) {
this.runCommand('run_wallet', {wallet_id: +wallet_id}, callback);
}
isValidRestoreWalletText(text, callback) {
this.runCommand('is_valid_restore_wallet_text', text, callback);
isValidRestoreWalletText(param, callback) {
this.runCommand('is_valid_restore_wallet_text', param, callback);
}
restoreWallet(path, pass, restore_key, callback) {
restoreWallet(path, pass, seed_phrase, seed_pass, callback) {
const params = {
restore_key: restore_key,
seed_phrase: seed_phrase,
path: path,
pass: pass
pass: pass,
seed_pass
};
this.runCommand('restore_wallet', params, callback);
}

View file

@ -62,6 +62,17 @@
</div>
</div>
<div class="input-block half-block">
<label for="seed-password">{{ 'RESTORE_WALLET.SEED_PASSWORD' | translate }}</label>
<input type="password" id="seed-password" formControlName="seedPassword" [attr.readonly]="this.seedPhraseInfo?.syntax_correct && this.seedPhraseInfo?.require_password ? null : true">
<div class="error-block" *ngIf="(restoreForm.controls['seedPassword'].dirty || restoreForm.controls['seedPassword'].touched) && !this.seedPhraseInfo?.hash_sum_matched">
<span>{{ 'RESTORE_WALLET.FORM_ERRORS.INCORRECT_PASSWORD' | translate }}</span>
</div>
<div class="success-block" *ngIf="this.seedPhraseInfo?.hash_sum_matched">
<span>{{ 'RESTORE_WALLET.OK' | translate }}</span>
</div>
</div>
<div class="wrap-buttons">
<button type="button" class="transparent-button" *ngIf="walletSaved" disabled><i class="icon"></i>{{walletSavedName}}</button>
<button type="button" class="blue-button select-button" (click)="saveWallet()" [disabled]="!restoreForm.valid" *ngIf="!walletSaved">{{ 'RESTORE_WALLET.BUTTON_SELECT' | translate }}</button>

View file

@ -1,42 +1,57 @@
import {Component, NgZone, OnInit} from '@angular/core';
import {FormGroup, FormControl, Validators} from '@angular/forms';
import {Router} from '@angular/router';
import {BackendService} from '../_helpers/services/backend.service';
import {VariablesService} from '../_helpers/services/variables.service';
import {ModalService} from '../_helpers/services/modal.service';
import {Wallet} from '../_helpers/models/wallet.model';
import {TranslateService} from '@ngx-translate/core';
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { BackendService } from '../_helpers/services/backend.service';
import { VariablesService } from '../_helpers/services/variables.service';
import { ModalService } from '../_helpers/services/modal.service';
import { Wallet } from '../_helpers/models/wallet.model';
import { TranslateService } from '@ngx-translate/core';
import { Subject } from 'rxjs/internal/Subject';
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-restore-wallet',
templateUrl: './restore-wallet.component.html',
styleUrls: ['./restore-wallet.component.scss']
styleUrls: ['./restore-wallet.component.scss'],
})
export class RestoreWalletComponent implements OnInit {
restoreForm = new FormGroup({
name: new FormControl('', [Validators.required, (g: FormControl) => {
for (let i = 0; i < this.variablesService.wallets.length; i++) {
if (g.value === this.variablesService.wallets[i].name) {
return {'duplicate': true};
}
}
return null;
}]),
key: new FormControl('', Validators.required),
password: new FormControl('', Validators.pattern(this.variablesService.pattern)),
confirm: new FormControl('')
}, function (g: FormGroup) {
return g.get('password').value === g.get('confirm').value ? null : {'confirm_mismatch': true};
});
export class RestoreWalletComponent implements OnInit, OnDestroy {
restoreForm = new FormGroup(
{
name: new FormControl('', [
Validators.required,
(g: FormControl) => {
for (let i = 0; i < this.variablesService.wallets.length; i++) {
if (g.value === this.variablesService.wallets[i].name) {
return { duplicate: true };
}
}
return null;
},
]),
key: new FormControl('', Validators.required),
password: new FormControl(
'',
Validators.pattern(this.variablesService.pattern)
),
confirm: new FormControl(''),
seedPassword: new FormControl('', Validators.pattern(this.variablesService.pattern)),
},
function (g: FormGroup) {
return g.get('password').value === g.get('confirm').value
? null
: { confirm_mismatch: true };
}
);
wallet = {
id: ''
id: '',
};
walletSaved = false;
walletSavedName = '';
progressWidth = '9rem';
seedPhraseInfo = null;
unsubscribeAll = new Subject<boolean>();
constructor(
private router: Router,
@ -47,7 +62,36 @@ export class RestoreWalletComponent implements OnInit {
private translate: TranslateService
) {}
ngOnInit() {}
ngOnInit() {
this.checkValidSeedPhrasePassword();
this.changeDetectionSeedPhrasePassword();
}
ngOnDestroy() {
this.unsubscribeAll.next(true);
this.unsubscribeAll.complete();
}
changeDetectionSeedPhrasePassword() {
this.restoreForm.controls.seedPassword.valueChanges
.pipe(debounceTime(0), distinctUntilChanged(), takeUntil(this.unsubscribeAll))
.subscribe(() => {
this.checkValidSeedPhrasePassword();
});
this.restoreForm.controls.key.valueChanges
.pipe(debounceTime(0), distinctUntilChanged(), takeUntil(this.unsubscribeAll))
.subscribe(() => {
this.checkValidSeedPhrasePassword();
});
}
checkValidSeedPhrasePassword() {
const seed_password = this.restoreForm.controls.seedPassword.value;
const seed_phrase = this.restoreForm.controls.key.value;
this.backend.getSeedPhraseInfo({seed_phrase, seed_password}, (status, data) => {
this.seedPhraseInfo = data;
});
}
createWallet() {
this.ngZone.run(() => {
@ -57,66 +101,133 @@ export class RestoreWalletComponent implements OnInit {
}
saveWallet() {
if (this.restoreForm.valid && this.restoreForm.get('name').value.length <= this.variablesService.maxWalletNameLength) {
this.backend.isValidRestoreWalletText(this.restoreForm.get('key').value, (valid_status, valid_data) => {
if (valid_data !== 'TRUE') {
this.ngZone.run(() => {
this.restoreForm.get('key').setErrors({key_not_valid: true});
});
} else {
this.backend.saveFileDialog(this.translate.instant('RESTORE_WALLET.CHOOSE_PATH'), '*', this.variablesService.settings.default_path, (save_status, save_data) => {
if (save_status) {
this.variablesService.settings.default_path = save_data.path.substr(0, save_data.path.lastIndexOf('/'));
this.walletSavedName = save_data.path.substr(save_data.path.lastIndexOf('/') + 1, save_data.path.length - 1);
this.backend.restoreWallet(save_data.path, this.restoreForm.get('password').value, this.restoreForm.get('key').value, (restore_status, restore_data) => {
if (restore_status) {
this.wallet.id = restore_data.wallet_id;
this.variablesService.opening_wallet = new Wallet(
restore_data.wallet_id,
this.restoreForm.get('name').value,
this.restoreForm.get('password').value,
restore_data['wi'].path,
restore_data['wi'].address,
restore_data['wi'].balance,
restore_data['wi'].unlocked_balance,
restore_data['wi'].mined_total,
restore_data['wi'].tracking_hey
if (
this.restoreForm.valid &&
this.restoreForm.get('name').value.length <=
this.variablesService.maxWalletNameLength
) {
this.backend.isValidRestoreWalletText(
{
seed_phrase: this.restoreForm.get('key').value,
seed_password: this.restoreForm.get('seedPassword').value,
},
(valid_status, valid_data) => {
if (valid_data !== 'TRUE') {
this.ngZone.run(() => {
this.restoreForm.get('key').setErrors({ key_not_valid: true });
});
} else {
this.backend.saveFileDialog(
this.translate.instant('RESTORE_WALLET.CHOOSE_PATH'),
'*',
this.variablesService.settings.default_path,
(save_status, save_data) => {
if (save_status) {
this.variablesService.settings.default_path = save_data.path.substr(
0,
save_data.path.lastIndexOf('/')
);
this.variablesService.opening_wallet.is_auditable = restore_data['wi'].is_auditable;
this.variablesService.opening_wallet.is_watch_only = restore_data['wi'].is_watch_only;
this.variablesService.opening_wallet.currentPage = 1;
this.variablesService.opening_wallet.alias = this.backend.getWalletAlias(this.variablesService.opening_wallet.address);
this.variablesService.opening_wallet.pages = new Array(1).fill(1);
this.variablesService.opening_wallet.totalPages = 1;
this.variablesService.opening_wallet.currentPage = 1;
this.variablesService.opening_wallet.total_history_item = 0;
this.variablesService.opening_wallet.restore = true;
if (restore_data.recent_history && restore_data.recent_history.history) {
this.variablesService.opening_wallet.totalPages = Math.ceil( restore_data.recent_history.total_history_items / this.variablesService.count);
this.variablesService.opening_wallet.totalPages > this.variablesService.maxPages
? this.variablesService.opening_wallet.pages = new Array(5).fill(1).map((value, index) => value + index)
: this.variablesService.opening_wallet.pages = new Array(this.variablesService.opening_wallet.totalPages).fill(1).map((value, index) => value + index);
this.variablesService.opening_wallet.prepareHistory(restore_data.recent_history.history);
}
this.backend.getContracts(this.variablesService.opening_wallet.wallet_id, (contracts_status, contracts_data) => {
if (contracts_status && contracts_data.hasOwnProperty('contracts')) {
this.ngZone.run(() => {
this.variablesService.opening_wallet.prepareContractsAfterOpen(contracts_data.contracts, this.variablesService.exp_med_ts, this.variablesService.height_app, this.variablesService.settings.viewedContracts, this.variablesService.settings.notViewedContracts);
});
this.walletSavedName = save_data.path.substr(
save_data.path.lastIndexOf('/') + 1,
save_data.path.length - 1
);
this.backend.restoreWallet(
save_data.path,
this.restoreForm.get('password').value,
this.restoreForm.get('key').value,
this.restoreForm.get('seedPassword').value,
(restore_status, restore_data) => {
if (restore_status) {
this.wallet.id = restore_data.wallet_id;
this.variablesService.opening_wallet = new Wallet(
restore_data.wallet_id,
this.restoreForm.get('name').value,
this.restoreForm.get('password').value,
restore_data['wi'].path,
restore_data['wi'].address,
restore_data['wi'].balance,
restore_data['wi'].unlocked_balance,
restore_data['wi'].mined_total,
restore_data['wi'].tracking_hey
);
this.variablesService.opening_wallet.is_auditable =
restore_data['wi'].is_auditable;
this.variablesService.opening_wallet.is_watch_only =
restore_data['wi'].is_watch_only;
this.variablesService.opening_wallet.currentPage = 1;
this.variablesService.opening_wallet.alias = this.backend.getWalletAlias(
this.variablesService.opening_wallet.address
);
this.variablesService.opening_wallet.pages = new Array(
1
).fill(1);
this.variablesService.opening_wallet.totalPages = 1;
this.variablesService.opening_wallet.currentPage = 1;
this.variablesService.opening_wallet.total_history_item = 0;
this.variablesService.opening_wallet.restore = true;
if (
restore_data.recent_history &&
restore_data.recent_history.history
) {
this.variablesService.opening_wallet.totalPages = Math.ceil(
restore_data.recent_history.total_history_items /
this.variablesService.count
);
this.variablesService.opening_wallet.totalPages >
this.variablesService.maxPages
? (this.variablesService.opening_wallet.pages = new Array(
5
)
.fill(1)
.map((value, index) => value + index))
: (this.variablesService.opening_wallet.pages = new Array(
this.variablesService.opening_wallet.totalPages
)
.fill(1)
.map((value, index) => value + index));
this.variablesService.opening_wallet.prepareHistory(
restore_data.recent_history.history
);
}
this.backend.getContracts(
this.variablesService.opening_wallet.wallet_id,
(contracts_status, contracts_data) => {
if (
contracts_status &&
contracts_data.hasOwnProperty('contracts')
) {
this.ngZone.run(() => {
this.variablesService.opening_wallet.prepareContractsAfterOpen(
contracts_data.contracts,
this.variablesService.exp_med_ts,
this.variablesService.height_app,
this.variablesService.settings
.viewedContracts,
this.variablesService.settings
.notViewedContracts
);
});
}
}
);
this.ngZone.run(() => {
this.walletSaved = true;
this.progressWidth = '50%';
});
} else {
this.modalService.prepareModal(
'error',
'RESTORE_WALLET.NOT_CORRECT_FILE_OR_PASSWORD'
);
}
}
});
this.ngZone.run(() => {
this.walletSaved = true;
this.progressWidth = '50%';
});
} else {
this.modalService.prepareModal('error', 'RESTORE_WALLET.NOT_CORRECT_FILE_OR_PASSWORD');
);
}
});
}
});
}
);
}
}
});
);
}
}
@ -132,7 +243,9 @@ export class RestoreWalletComponent implements OnInit {
if (!exists) {
this.backend.runWallet(this.wallet.id, (run_status, run_data) => {
if (run_status) {
this.variablesService.wallets.push(this.variablesService.opening_wallet);
this.variablesService.wallets.push(
this.variablesService.opening_wallet
);
if (this.variablesService.appPass) {
this.backend.storeSecureAppData();
}
@ -145,7 +258,10 @@ export class RestoreWalletComponent implements OnInit {
});
} else {
this.variablesService.opening_wallet = null;
this.modalService.prepareModal('error', 'OPEN_WALLET.WITH_ADDRESS_ALREADY_OPEN');
this.modalService.prepareModal(
'error',
'OPEN_WALLET.WITH_ADDRESS_ALREADY_OPEN'
);
this.backend.closeWallet(this.wallet.id, () => {
this.ngZone.run(() => {
this.router.navigate(['/']);

View file

@ -32,9 +32,9 @@ export class SeedPhraseComponent implements OnInit, OnDestroy {
if (params.wallet_id) {
this.wallet_id = params.wallet_id;
this.backend.getSmartWalletInfo(params.wallet_id, (status, data) => {
if (data.hasOwnProperty('restore_key')) {
if (data.hasOwnProperty('seed_phrase')) {
this.ngZone.run(() => {
this.seedPhrase = data['restore_key'].trim();
this.seedPhrase = data['seed_phrase'].trim();
});
}
});

View file

@ -15,8 +15,10 @@
<div class="input-block">
<label for="wallet-name">{{ 'WALLET_DETAILS.LABEL_NAME' | translate }}</label>
<input type="text" id="wallet-name" formControlName="name" [maxLength]="variablesService.maxWalletNameLength" (contextmenu)="variablesService.onContextMenu($event)">
<div class="error-block" *ngIf="detailsForm.controls['name'].invalid && (detailsForm.controls['name'].dirty || detailsForm.controls['name'].touched)">
<input type="text" id="wallet-name" formControlName="name" [maxLength]="variablesService.maxWalletNameLength"
(contextmenu)="variablesService.onContextMenu($event)">
<div class="error-block"
*ngIf="detailsForm.controls['name'].invalid && (detailsForm.controls['name'].dirty || detailsForm.controls['name'].touched)">
<div *ngIf="detailsForm.controls['name'].errors['required']">
{{ 'WALLET_DETAILS.FORM_ERRORS.NAME_REQUIRED' | translate }}
</div>
@ -33,24 +35,57 @@
<label for="wallet-location">{{ 'WALLET_DETAILS.LABEL_FILE_LOCATION' | translate }}</label>
<input type="text" id="wallet-location" formControlName="path" readonly>
</div>
<div class="input-block textarea">
<label for="seed-phrase">{{ 'WALLET_DETAILS.LABEL_SEED_PHRASE' | translate }}</label>
<div class="seed-phrase" id="seed-phrase">
<div class="seed-phrase-hint" (click)="showSeedPhrase()" *ngIf="!showSeed">{{ 'WALLET_DETAILS.SEED_PHRASE_HINT' | translate }}</div>
<div class="seed-phrase-content" *ngIf="showSeed" (contextmenu)="variablesService.onContextMenuOnlyCopy($event, seedPhrase)">
<ng-container *ngFor="let word of seedPhrase.split(' '); let index = index">
<div class="word">{{(index + 1) + '. ' + word}}</div>
</ng-container>
</div>
</div>
</div>
<div class="wallet-buttons">
<button type="submit" class="blue-button" [disabled]="!detailsForm.valid">{{ 'WALLET_DETAILS.BUTTON_SAVE' | translate }}</button>
<button type="button" class="blue-button" (click)="closeWallet()">{{ 'WALLET_DETAILS.BUTTON_REMOVE' | translate }}</button>
</div>
</form>
</div>
<ng-container *ngIf="!showSeed else seedPhraseContent">
<form class="form-seed mt-2" [formGroup]="seedPhraseForm" (ngSubmit)="onSubmitSeed()">
<label>{{ 'WALLET_DETAILS.LABEL_SEED_PHRASE' | translate }}</label>
<div class="form-content">
<div class="seed-phrase-form">
<div class="input-block">
<label for="create-password">{{ 'WALLET_DETAILS.CREATE_PASSWORD_SECURE' | translate }} (<a class="text-coral">{{ 'WALLET_DETAILS.INFO' | translate }}</a>)</label>
<input type="password" id="create-password" formControlName="password">
</div>
<div class="input-block">
<label for="confirm-password">{{ 'WALLET_DETAILS.FORM.CONFIRM_PASSWORD' | translate }}</label>
<input type="password" id="confirm-password" formControlName="confirmPassword">
</div>
<span class="error-message" *ngIf="!seedPhraseForm.valid">
{{ 'WALLET_DETAILS.FORM_ERRORS.PASSWORDS_DONT_MATCH' | translate }}
</span>
<button type="submit" class="blue-button" [disabled]="!seedPhraseForm.valid"><i class="icon safety"></i>
{{ 'WALLET_DETAILS.FORM.GENERATE_SECURE_SEED' | translate }}</button>
<a class="secured-seed"><i class="icon info"></i>{{ 'WALLET_DETAILS.FORM.SECURED_SEED_WILL_REQUIRE' | translate }}</a>
</div>
</div>
</form>
</ng-container>
<ng-template #seedPhraseContent>
<div class="seed-phrase mt-2">
<div class="seed-phrase-title">
<span>{{ 'WALLET_DETAILS.LABEL_SEED_PHRASE' | translate }}</span>
<p class="right-part">
<i class="icon" [class.copy]="!copyAnimation" [class.copied]="copyAnimation" (click)="copySeedPhrase()"></i>
<span
*ngIf="seedPhraseForm.controls.password.value.length == 0">{{ 'WALLET_DETAILS.SEED_IS_UNSECURED' | translate }} <i class="icon unsecured"></i></span>
<span
*ngIf="seedPhraseForm.controls.password.value.length > 0">{{ 'WALLET_DETAILS.SEED_IS_SECURED' | translate }} <i class="icon secured"></i></span>
</p>
</div>
<div class="seed-phrase-content">
<ng-container *ngFor="let word of seedPhrase.split(' '); let index = index">
<div class="item"
[class.dark]="(index + 1) >= 1 && (index + 1) <= 7 || (index + 1) >= 15 && (index + 1) <= 21">
{{(index + 1) + '. ' + word}}</div>
</ng-container>
</div>
<div class="seed-phrase-footer"
*ngIf="seedPhraseForm.controls.password.value.length > 0">
<span class="title">{{ 'WALLET_DETAILS.REMEMBER_YOU_WILL_REQUIRE' | translate }}</span>
</div>
</div>
</ng-template>
</div>

View file

@ -8,32 +8,6 @@
}
}
.seed-phrase {
display: flex;
font-size: 1.4rem;
line-height: 1.5rem;
padding: 1.4rem;
width: 100%;
height: 8.8rem;
.seed-phrase-hint {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
width: 100%;
height: 100%;
}
.seed-phrase-content {
display: flex;
flex-direction: column;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
}
.wallet-buttons {
display: flex;
align-items: center;
@ -45,5 +19,190 @@
max-width: 15rem;
}
}
}
.mt-2 {
margin-top: 2rem;
}
.form-seed {
label {
color: #556576;
font-size: 1.3rem;
line-height: 2.4rem;
}
.form-content {
border: 2px solid #2b3644;
display: flex;
justify-content: center;
padding: 3rem;
width: 100%;
}
.text-coral {
color: #4db1ff;
text-decoration: none;
}
.seed-phrase-form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 38rem;
width: 100%;
.input-block,
button {
width: 100%;
}
.error-message {
margin-top: 1rem;
color: #ff6f00;
}
.secured-seed {
color: #e0e0e0;
font-size: 1.2rem;
line-height: 2.4rem;
text-decoration: none;
display: flex;
align-items: center;
cursor: pointer;
.icon {
margin-right: 1.2rem;
}
}
button {
display: flex;
justify-content: center;
align-items: center;
margin-top: 3rem;
margin-bottom: 2rem;
.icon {
margin-right: 1.2rem;
}
}
}
}
.seed-phrase {
display: flex;
flex-direction: column;
width: 100%;
background-color: transparent !important;
&-title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.right-part {
display: inline-flex;
align-items: center;
.icon {
cursor: pointer;
width: 1.7rem;
height: 1.7rem;
&.copy {
background-color: #4caefb;
margin-right: 1.2rem;
mask: url(~src/assets/icons/copy.svg) no-repeat center;
&:hover {
opacity: 0.75;
}
}
&.copied {
margin-right: 1.2rem;
background-color: #4caefb;
mask: url(~src/assets/icons/complete-testwallet.svg) no-repeat center;
}
&.secured, &.unsecured {
margin-left: 1.2rem;
}
}
span {
color: #556576;
font-size: 1.3rem;
line-height: 2.4rem;
display: inline-flex;
align-items: center;
.icon {
margin-left: 1.2rem;
}
}
}
}
&-content {
border-top: 2px solid #2b3644;
border-bottom: 2px solid #2b3644;
padding: 1rem 0 1rem;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 1rem;
.item {
display: flex;
align-items: center;
padding: 0 2rem;
width: calc(100% / 7);
min-height: 35px;
font-size: 1.3rem;
color: #e0e0e0;
&.dark {
background-color: #18202a;
}
}
}
&-footer {
text-align: center;
.title {
color: #556576;
font-size: 1.3rem;
line-height: 2.4rem;
}
}
}
.icon {
display: inline-flex;
width: 1.6rem;
height: 1.6rem;
&.secured {
background-color: #5cda9d;
mask: url(~src/assets/icons/secured.svg) no-repeat center;
}
&.info {
background-color: #4caefb;
mask: url(~src/assets/icons/info.svg) no-repeat center;
}
&.unsecured {
background-color: #ff6f00;
mask: url(~src/assets/icons/unsecured.svg) no-repeat center;
}
&.safety {
background-color: #111921;
mask: url(~src/assets/icons/safety.svg) no-repeat center;
}
}

View file

@ -1,35 +1,64 @@
import {Component, NgZone, OnDestroy, OnInit} from '@angular/core';
import {FormGroup, FormControl, Validators} from '@angular/forms';
import {BackendService} from '../_helpers/services/backend.service';
import {VariablesService} from '../_helpers/services/variables.service';
import {Router} from '@angular/router';
import {Location} from '@angular/common';
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { BackendService } from '../_helpers/services/backend.service';
import { VariablesService } from '../_helpers/services/variables.service';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
@Component({
selector: 'app-wallet-details',
templateUrl: './wallet-details.component.html',
styleUrls: ['./wallet-details.component.scss']
styleUrls: ['./wallet-details.component.scss'],
})
export class WalletDetailsComponent implements OnInit, OnDestroy {
seedPhrase = '';
showSeed = false;
copyAnimation = false;
copyAnimationTimeout;
detailsForm = new FormGroup({
name: new FormControl('', [Validators.required, (g: FormControl) => {
for (let i = 0; i < this.variablesService.wallets.length; i++) {
if (g.value === this.variablesService.wallets[i].name) {
if (this.variablesService.wallets[i].wallet_id === this.variablesService.currentWallet.wallet_id) {
return {'same': true};
} else {
return {'duplicate': true};
name: new FormControl('', [
Validators.required,
(g: FormControl) => {
for (let i = 0; i < this.variablesService.wallets.length; i++) {
if (g.value === this.variablesService.wallets[i].name) {
if (
this.variablesService.wallets[i].wallet_id ===
this.variablesService.currentWallet.wallet_id
) {
return { same: true };
} else {
return { duplicate: true };
}
}
}
}
return null;
}]),
path: new FormControl('')
return null;
},
]),
path: new FormControl(''),
});
seedPhraseForm = new FormGroup(
{
password: new FormControl(
'',
Validators.pattern(this.variablesService.pattern)
),
confirmPassword: new FormControl(
'',
Validators.pattern(this.variablesService.pattern)
),
},
{ validators: this.checkPasswords }
);
checkPasswords(group: FormGroup) {
const pass = group.controls.password.value;
const confirmPass = group.controls.confirmPassword.value;
return pass === confirmPass ? null : { notSame: true };
}
constructor(
private router: Router,
private backend: BackendService,
@ -40,55 +69,91 @@ export class WalletDetailsComponent implements OnInit, OnDestroy {
ngOnInit() {
this.showSeed = false;
this.detailsForm.get('name').setValue(this.variablesService.currentWallet.name);
this.detailsForm.get('path').setValue(this.variablesService.currentWallet.path);
this.backend.getSmartWalletInfo(this.variablesService.currentWallet.wallet_id, (status, data) => {
if (data.hasOwnProperty('restore_key')) {
this.ngZone.run(() => {
this.seedPhrase = data['restore_key'].trim();
});
}
});
this.detailsForm
.get('name')
.setValue(this.variablesService.currentWallet.name);
this.detailsForm
.get('path')
.setValue(this.variablesService.currentWallet.path);
}
showSeedPhrase() {
this.showSeed = true;
}
onSubmitSeed() {
if (this.seedPhraseForm.valid) {
this.showSeedPhrase();
const wallet_id = this.variablesService.currentWallet.wallet_id;
const seed_password = this.seedPhraseForm.controls.password.value;
this.backend.getSmartWalletInfo(
{ wallet_id, seed_password },
(status, data) => {
if (data.hasOwnProperty('seed_phrase')) {
this.ngZone.run(() => {
this.seedPhrase = data['seed_phrase'].trim();
});
}
}
);
}
}
onSubmitEdit() {
if (this.detailsForm.value) {
this.variablesService.currentWallet.name = this.detailsForm.get('name').value;
this.variablesService.currentWallet.name = this.detailsForm.get(
'name'
).value;
this.ngZone.run(() => {
this.router.navigate(['/wallet/' + this.variablesService.currentWallet.wallet_id]);
this.router.navigate([
'/wallet/' + this.variablesService.currentWallet.wallet_id,
]);
});
}
}
closeWallet() {
this.backend.closeWallet(this.variablesService.currentWallet.wallet_id, () => {
for (let i = this.variablesService.wallets.length - 1; i >= 0; i--) {
if (this.variablesService.wallets[i].wallet_id === this.variablesService.currentWallet.wallet_id) {
this.variablesService.wallets.splice(i, 1);
this.backend.closeWallet(
this.variablesService.currentWallet.wallet_id,
() => {
for (let i = this.variablesService.wallets.length - 1; i >= 0; i--) {
if (
this.variablesService.wallets[i].wallet_id ===
this.variablesService.currentWallet.wallet_id
) {
this.variablesService.wallets.splice(i, 1);
}
}
this.ngZone.run(() => {
if (this.variablesService.wallets.length) {
this.variablesService.currentWallet = this.variablesService.wallets[0];
this.router.navigate([
'/wallet/' + this.variablesService.currentWallet.wallet_id,
]);
} else {
this.router.navigate(['/']);
}
});
if (this.variablesService.appPass) {
this.backend.storeSecureAppData();
}
}
this.ngZone.run(() => {
if (this.variablesService.wallets.length) {
this.variablesService.currentWallet = this.variablesService.wallets[0];
this.router.navigate(['/wallet/' + this.variablesService.currentWallet.wallet_id]);
} else {
this.router.navigate(['/']);
}
});
if (this.variablesService.appPass) {
this.backend.storeSecureAppData();
}
});
);
}
back() {
this.location.back();
}
ngOnDestroy() {}
copySeedPhrase() {
this.backend.setClipboard(this.seedPhrase);
this.copyAnimation = true;
this.copyAnimationTimeout = window.setTimeout(() => {
this.copyAnimation = false;
}, 2000);
}
ngOnDestroy() {
clearTimeout(this.copyAnimationTimeout);
}
}

View file

@ -120,13 +120,16 @@
"BUTTON_CREATE": "Create wallet",
"NOT_CORRECT_FILE_OR_PASSWORD": "Invalid wallet file or password does not match",
"CHOOSE_PATH": "Please choose a path",
"SEED_PASSWORD": "Seed password",
"OK": "OK",
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_DUPLICATE": "Name is duplicate",
"MAX_LENGTH": "Maximum name length reached",
"CONFIRM_NOT_MATCH": "Confirm password not match",
"KEY_REQUIRED": "Key is required",
"KEY_NOT_VALID": "Key not valid"
"KEY_NOT_VALID": "Key not valid",
"INCORRECT_PASSWORD": "Incorrect password"
}
},
"SEED_PHRASE": {
@ -214,10 +217,21 @@
"SEED_PHRASE_HINT": "Click to reveal the seed phrase",
"BUTTON_SAVE": "Save",
"BUTTON_REMOVE": "Close wallet",
"CREATE_PASSWORD_SECURE": "Create a password to secure your seed",
"INFO": "info",
"SEED_IS_UNSECURED": "Seed is unsecured",
"SEED_IS_SECURED": "Seed is secured",
"REMEMBER_YOU_WILL_REQUIRE": "Remember, you will require the password to restore it.",
"FORM": {
"CONFIRM_PASSWORD": "Confirm password",
"GENERATE_SECURE_SEED": "Generate Secure Seed",
"SECURED_SEED_WILL_REQUIRE": "Secured seed will require this password to restore."
},
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_DUPLICATE": "Name is duplicate",
"MAX_LENGTH": "Maximum name length reached"
"MAX_LENGTH": "Maximum name length reached",
"PASSWORDS_DONT_MATCH": "Passwords don't match"
}
},
"ASSIGN_ALIAS": {

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 384"><defs><style>.cls-1{fill:#fff;}</style></defs><title>info</title><path class="cls-1" d="M192,42.05476A149.94522,149.94522,0,0,1,298.02728,298.02728,149.94522,149.94522,0,1,1,85.97271,85.97271,148.96374,148.96374,0,0,1,192,42.05476M192,0C85.96132,0,0,85.96132,0,192S85.96132,384,192,384s192-85.96132,192-192S298.03868,0,192,0Z"/><rect class="cls-1" x="171" y="247.94524" width="42" height="42"/><rect class="cls-1" x="171" y="94.05476" width="42" height="120"/></svg>

After

Width:  |  Height:  |  Size: 561 B

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 320 384" style="enable-background:new 0 0 320 384;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_2_);}
.st1{clip-path:url(#SVGID_2_);fill:none;stroke:#000000;stroke-width:42;stroke-miterlimit:10;}
</style>
<g>
<defs>
<rect id="SVGID_1_" width="320" height="384"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<path class="st0" d="M159.9,44.5L278,85.8V232c0,0.9-0.4,9.2-14.7,26c-11,13-26.9,27.1-47.3,42c-20.6,15.1-41.6,27.8-56.1,36.1
c-14.5-8.3-35.4-21-56-36.1c-20.3-14.9-36.2-29-47.2-42C42.4,241.2,42,232.9,42,232V85.8L159.9,44.5 M159.9,0L0,56v176
c0,70.7,159.9,152,159.9,152S320,302.7,320,232V56L159.9,0z"/>
<polyline class="st1" points="100.1,182.2 142.3,218.5 220.3,145.1 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M26,15v-5c0-5.5-4.5-10-10-10h-0.1c-5.5,0-10,4.5-10,10v5H3v17h12.9H16h13V15H26z M9.9,10c0-3.3,2.7-6,6-6H16
c3.3,0,6,2.7,6,6v5H9.9V10z M14,27v-7h4v7H14z"/>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M22,15h-3.4H9.9v-5c0-3.3,2.7-6,6-6H16c3,0,5.4,2.2,5.9,5h4c-0.5-5-4.8-9-9.9-9h-0.1c-5.5,0-10,4.5-10,10v5H3
v17h12.9H16h13V15h-3H22z M18,27h-4v-7h4V27z"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

View file

@ -205,6 +205,16 @@ button {
color: themed(redTextColor);
}
}
.success-block {
font-size: 1rem;
line-height: 1.4rem;
align-self: flex-end;
text-align: right;
@include themify($themes) {
color: themed(greenTextColor);
}
}
}
.error-text {