1
0
Fork 0
forked from lthn/blockchain

Lock/unlock transaction + contact (#86)

* test details & close btn

* compiled

* confirm modal

* delete "cancel" btn from aliases

* fix copy btn

* text alias fix

* confirm pop up

* lock & unlock transaction

* confirm pop up comment fix

* compiled

* rebuild html

* contact service

* rebuild html

* fix add contact + rebuild html
This commit is contained in:
zetov 2019-08-14 11:16:56 +03:00 committed by cryptozoidberg
parent 9a081ddbf0
commit 53d5b2362a
99 changed files with 3004 additions and 122 deletions

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -38,6 +38,7 @@
"MESSAGES": "New offers/Messages",
"SYNCING": "Syncing wallet"
},
"CONTACTS": "Contacts",
"SETTINGS": "Settings",
"LOG_OUT": "Log out",
"SYNCHRONIZATION": {
@ -192,18 +193,17 @@
},
"ASSIGN_ALIAS": {
"NAME": {
"LABEL": "Unique name",
"LABEL": "Alias",
"PLACEHOLDER": "@ Enter alias",
"TOOLTIP": "An alias is a shortened form or your account. An alias can only include Latin letters, numbers and characters “.” and “-”. It must start with “@”."
},
"COMMENT": {
"LABEL": "Comment",
"PLACEHOLDER": "Enter comment",
"PLACEHOLDER": "",
"TOOLTIP": "The comment will be visible to anyone who wants to make a payment to your alias. You can provide details about your business, contacts, or include any text. Comments can be edited later."
},
"COST": "Cost to create alias {{value}} {{currency}}",
"COST": "Alias fee {{value}} {{currency}}",
"BUTTON_ASSIGN": "Assign",
"BUTTON_CANCEL": "Cancel",
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_WRONG": "Alias has wrong name",
@ -217,40 +217,39 @@
},
"EDIT_ALIAS": {
"NAME": {
"LABEL": "Unique name",
"LABEL": "Alias",
"PLACEHOLDER": "@ Enter alias"
},
"COMMENT": {
"LABEL": "Comment",
"PLACEHOLDER": "Enter comment"
"PLACEHOLDER": ""
},
"FORM_ERRORS": {
"NO_MONEY": "You do not have enough funds to change the comment to this alias",
"MAX_LENGTH": "Maximum comment length reached"
},
"COST": "Cost to edit alias {{value}} {{currency}}",
"BUTTON_EDIT": "Edit",
"BUTTON_CANCEL": "Cancel"
"COST": "Fee {{value}} {{currency}}",
"BUTTON_EDIT": "Edit"
},
"TRANSFER_ALIAS": {
"NAME": {
"LABEL": "Unique name",
"LABEL": "Alias",
"PLACEHOLDER": "@ Enter alias"
},
"COMMENT": {
"LABEL": "Comment",
"PLACEHOLDER": "Enter comment"
"PLACEHOLDER": ""
},
"ADDRESS": {
"LABEL": "The account to which the alias will be transferred",
"PLACEHOLDER": "Enter wallet address"
"LABEL": "Transfer to",
"PLACEHOLDER": ""
},
"FORM_ERRORS": {
"WRONG_ADDRESS": "No wallet with this account exists",
"ALIAS_EXISTS": "This account already has an alias",
"NO_MONEY": "You do not have enough funds to transfer this alias"
},
"COST": "Cost to transfer alias {{value}} {{currency}}",
"COST": "Transfer fee {{value}} {{currency}}",
"BUTTON_TRANSFER": "Transfer",
"BUTTON_CANCEL": "Cancel",
"REQUEST_SEND_REG": "The alias will be transferred within 10 minutes"
@ -453,6 +452,17 @@
"INFO": "Information",
"OK": "OK"
},
"CONFIRM": {
"BUTTON_CONFIRM": "Send",
"BUTTON_CANCEL": "Cancel",
"TITLE": "Confirm transaction",
"MESSAGE": {
"SEND": "Send",
"FROM": "From",
"TO": "To",
"COMMENT": "Comment"
}
},
"STAKING": {
"TITLE": "Staking",
"TITLE_PENDING": "Pending",
@ -478,6 +488,55 @@
"OFF": "OFF"
}
},
"CONTACTS": {
"TITLE": "Contact list",
"IMPORT_EXPORT": "Import or export contacts",
"IMPORT": "Import",
"EXPORT": "Export",
"ADD": "Add/edit contact",
"SEND": "Send",
"SEND_FROM": "Send from",
"SEND_TO": "To",
"OPEN_ADD_WALLET": "Open/Add wallet",
"COPY": "- Copy"
, "TABLE": {
"NAME": "Name",
"ALIAS": "Alias",
"ADDRESS": "Address",
"NOTES": "Notes",
"EMPTY": "Contact list is empty"
},
"FORM": {
"NAME": "Name",
"ADDRESS": "Address",
"NOTES": "Notes"
},
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_DUBLICATED": "Name is dublicated",
"ADDRESS_REQUIRED": "Address is required",
"ADDRESS_NOT_VALID": "Address not valid",
"SET_MASTER_PASSWORD": "Set master password",
"ADDRESS_DUBLICATED": "Address is dublicated",
"MAX_LENGTH": "Maximum notes length reached",
"NAME_WRONG": "Contact has wrong name",
"NAME_LENGTH": "The name must be 4-25 characters long"
},
"BUTTON": {
"SEND": "Send",
"EDIT": "Edit",
"DELETE": "Delete",
"ADD": "Add contact",
"ADD_EDIT": "Add/Save",
"GO_TO_WALLET": "Go to wallet",
"IMPORT_EXPORT": "Import/export"
},
"SUCCESS_SENT": "Contact added",
"SUCCESS_SAVE": "Contact is edited",
"SUCCESS_IMPORT": "Contacts is imported",
"ERROR_IMPORT": "Error is occured while reading file!",
"ERROR_TYPE_FILE": "Please import valid .csv file."
},
"ERRORS": {
"NO_MONEY": "Not enough money",
"NOT_ENOUGH_MONEY": "Insufficient funds in account",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -5,6 +5,7 @@
"MASTER_PASS": "Master password",
"BUTTON_NEXT": "Next",
"BUTTON_SKIP": "Skip",
"BUTTON_RESET": "Reset",
"INCORRECT_PASSWORD": "Invalid password",
"FORM_ERRORS": {
"PASS_REQUIRED": "Password is required",

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, 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 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<title>icons_5</title>
<g id="f31d9964-f67b-451c-9eb6-78b833647305">
<path class="st0" d="M299.9,197.6c9.3-16.7,14.2-35.5,14.1-54.6c0-70-43.4-105-97-105s-97,35-97,105c-0.1,19.1,4.8,38,14.2,54.6
C89.2,218.8,68.5,274.1,50,346l334-0.1C365.5,274,344.8,218.8,299.9,197.6z M178.1,94c11.6-11.6,27.6-14,38.9-14s27.3,2.4,38.9,14
c13.3,13.2,16.1,34,16.1,49c0,34.7-24.7,63-55,63s-55-28.3-55-63C162,115.7,170.7,101.3,178.1,94z M128.8,256.5
c10.1-14.3,21.1-22.1,36.1-25c31.4,22,73.1,22,104.5-0.1c14.9,2.9,25.9,10.8,35.9,25c8.9,12.6,16.3,29.3,22.7,47.5L106.1,304
C112.5,285.8,119.9,269.1,128.8,256.5z"/>
<path class="st0" d="M32.5,261H0v42h18.2C23,287,27.6,273.3,32.5,261z"/>
<path class="st0" d="M83,182c-0.9-3.6-1.7-7.3-2.4-11H0v42h56.8C64.2,201.6,73,191.2,83,182z"/>
<path class="st0" d="M88.8,81H0v42h79C80.4,108.6,83.7,94.5,88.8,81z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M371,76H269V14H115v62H13v42h35v266h288V118h35V76z M157,56h70v20h-70V56z M294,342H90V118h204V342z"/>
<rect x="136" y="166" class="st0" width="42" height="128"/>
<rect x="206" y="166" class="st0" width="42" height="128"/>
</svg>

After

Width:  |  Height:  |  Size: 651 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path id="details_1_" class="st0" d="M384,112.9L271.1,0L25,246.1v83.8L0.3,354l29.3,30l25.6-25h82.6L384,112.9z M324.6,112.9
l-36.4,36.4l-53.5-53.5l36.4-36.4L324.6,112.9z M70.3,317l-3.3-3.5v-50l138-138l53.5,53.5l-138,138H70.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<polygon class="st0" points="296.5,366 384,288 296.5,210.5 296.5,267 0,267 0,309 296.5,309 "/>
<polygon class="st0" points="87.5,18 0,96 87.5,173.5 87.5,117 384,117 384,75 87.5,75 "/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.4, 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

@ -997,3 +997,29 @@ app-open-wallet-modal {
}
}
}
app-send-modal {
.modal {
@include themify($themes) {
background: themed(modalBackground);
color: themed(mainTextColor);
}
.title {
@include themify($themes) {
border-bottom: 0.2rem solid themed(transparentButtonBorderColor);
}
}
.action-button {
@include themify($themes) {
background-color: themed(blueTextColor);
color: themed(alternativeTextColor);
}
}
}
}

View file

@ -0,0 +1,88 @@
app-contacts, app-add-contacts,
app-contact-send, app-export-import {
flex: 1 1 auto;
padding: 3rem;
min-width: 85rem;
.content {
position: relative;
padding: 3rem;
min-height: 100%;
@include themify($themes) {
background-color: themed(contentBackgroundColor);
color: themed(mainTextColor);
}
.head {
position: absolute;
top: 0;
left: 0;
}
}
}
app-contacts {
table {
.alias {
@include themify($themes) {
color: themed(blueTextColor);
}
}
button {
.icon {
@include themify($themes) {
background-color: themed(blueTextColor);
}
}
span {
@include themify($themes) {
color: themed(mainTextColor)
}
}
}
}
.footer {
@include themify($themes) {
color: themed(blueTextColor);
}
.import-btn {
@include themify($themes) {
color: themed(blueTextColor);
}
.icon {
@include themify($themes) {
background-color: themed(blueTextColor);
}
}
}
}
}
app-contact-send {
.wallets-selection {
button {
@include themify($themes) {
color: themed(blueTextColor);
}
}
}
}

View file

@ -199,6 +199,13 @@ app-history {
}
}
.unlock-transaction {
@include themify($themes) {
background-color: themed(blueTextColor);
}
}
.status.send {
.status-transaction {

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, 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 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<title>icons_5</title>
<g id="f31d9964-f67b-451c-9eb6-78b833647305">
<path class="st0" d="M299.9,197.6c9.3-16.7,14.2-35.5,14.1-54.6c0-70-43.4-105-97-105s-97,35-97,105c-0.1,19.1,4.8,38,14.2,54.6
C89.2,218.8,68.5,274.1,50,346l334-0.1C365.5,274,344.8,218.8,299.9,197.6z M178.1,94c11.6-11.6,27.6-14,38.9-14s27.3,2.4,38.9,14
c13.3,13.2,16.1,34,16.1,49c0,34.7-24.7,63-55,63s-55-28.3-55-63C162,115.7,170.7,101.3,178.1,94z M128.8,256.5
c10.1-14.3,21.1-22.1,36.1-25c31.4,22,73.1,22,104.5-0.1c14.9,2.9,25.9,10.8,35.9,25c8.9,12.6,16.3,29.3,22.7,47.5L106.1,304
C112.5,285.8,119.9,269.1,128.8,256.5z"/>
<path class="st0" d="M32.5,261H0v42h18.2C23,287,27.6,273.3,32.5,261z"/>
<path class="st0" d="M83,182c-0.9-3.6-1.7-7.3-2.4-11H0v42h56.8C64.2,201.6,73,191.2,83,182z"/>
<path class="st0" d="M88.8,81H0v42h79C80.4,108.6,83.7,94.5,88.8,81z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M371,76H269V14H115v62H13v42h35v266h288V118h35V76z M157,56h70v20h-70V56z M294,342H90V118h204V342z"/>
<rect x="136" y="166" class="st0" width="42" height="128"/>
<rect x="206" y="166" class="st0" width="42" height="128"/>
</svg>

After

Width:  |  Height:  |  Size: 651 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path id="details_1_" class="st0" d="M384,112.9L271.1,0L25,246.1v83.8L0.3,354l29.3,30l25.6-25h82.6L384,112.9z M324.6,112.9
l-36.4,36.4l-53.5-53.5l36.4-36.4L324.6,112.9z M70.3,317l-3.3-3.5v-50l138-138l53.5,53.5l-138,138H70.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<polygon class="st0" points="296.5,366 384,288 296.5,210.5 296.5,267 0,267 0,309 296.5,309 "/>
<polygon class="st0" points="87.5,18 0,96 87.5,173.5 87.5,117 384,117 384,75 87.5,75 "/>
</svg>

After

Width:  |  Height:  |  Size: 592 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

@ -5800,8 +5800,8 @@ __webpack_require__.r(__webpack_exports__);
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(/*! E:\Zano\check\zano\src\gui\qt-daemon\html_source\src\polyfills.ts */"./src/polyfills.ts");
module.exports = __webpack_require__(/*! E:\Zano\check\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");
__webpack_require__(/*! d:\Projects_now\ZANO\zano\src\gui\qt-daemon\html_source\src\polyfills.ts */"./src/polyfills.ts");
module.exports = __webpack_require__(/*! d:\Projects_now\ZANO\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

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 23.0.4, 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 one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -33,6 +33,7 @@
"idlejs": "^2.0.1",
"json-bignumber": "^1.0.1",
"ngx-contextmenu": "^5.1.1",
"ngx-papaparse": "^3.0.3",
"qrcode": "^1.3.0",
"rxjs": "~6.3.3",
"zone.js": "~0.8.26"

View file

@ -0,0 +1,6 @@
export class Contact {
name: string;
address: string;
alias?;
notes: string;
}

View file

@ -313,11 +313,17 @@ export class BackendService {
this.runCommand('check_master_password', pass, callback);
}
storeSecureAppData(callback?) {
let data;
const wallets = [];
const contacts = [];
this.variablesService.wallets.forEach((wallet) => {
wallets.push({name: wallet.name, pass: wallet.pass, path: wallet.path, staking: wallet.staking});
});
this.backendObject['store_secure_app_data'](JSON.stringify(wallets), this.variablesService.appPass, (dataStore) => {
this.variablesService.contacts.forEach((contact) => {
contacts.push({name: contact.name, address: contact.address, notes: contact.notes});
});
data = {wallets: wallets, contacts: contacts};
this.backendObject['store_secure_app_data'](JSON.stringify(data), this.variablesService.appPass, (dataStore) => {
this.backendCallback(dataStore, {}, callback, 'store_secure_app_data');
});
}
@ -352,6 +358,14 @@ export class BackendService {
this.runCommand('show_openfile_dialog', params, callback);
}
storeFile(path, buff) {
this.backendObject['store_to_file'](path, (typeof buff === 'string' ? buff : JSON.stringify(buff)));
}
loadFile(path, callback) {
this.runCommand('load_from_file', path, callback);
}
generateWallet(path, pass, callback) {
const params = {
path: path,
@ -601,6 +615,22 @@ export class BackendService {
return {};
}
getContactAlias() {
if (this.variablesService.contacts.length && this.variablesService.daemon_state === 2) {
this.variablesService.contacts.map(contact => {
this.getAliasByAddress(contact.address, (status, data) => {
if (status) {
if (data.alias) {
contact.alias = '@' + data.alias;
}
} else {
contact.alias = null;
}
});
});
}
}
getPoolInfo(callback) {
this.runCommand('get_tx_pool_info', {}, callback);
}

View file

@ -1,5 +1,6 @@
import {Injectable, NgZone} from '@angular/core';
import {Wallet} from '../models/wallet.model';
import {Contact} from '../models/contact.model';
import {BehaviorSubject} from 'rxjs';
import {Idle} from 'idlejs/dist';
import {Router} from '@angular/router';
@ -45,12 +46,16 @@ export class VariablesService {
public wallets: Array<Wallet> = [];
public currentWallet: Wallet;
public selectWallet: number;
public aliases: any = [];
public aliasesChecked: any = {};
public enableAliasSearch = false;
public maxWalletNameLength = 25;
public maxCommentLength = 255;
public dataIsLoaded = false;
public dataIsLoaded = false;
public contacts: Array<Contact> = [];
public newContact: Contact = {name: null, address: null, notes: null};
getExpMedTsEvent = new BehaviorSubject(null);
getHeightAppEvent = new BehaviorSubject(null);

View file

@ -0,0 +1,69 @@
<div class="content scrolled-content">
<div class="head">
<div class="breadcrumbs">
<span [routerLink]="['/contacts']">{{ 'CONTACTS.TITLE' | translate }}</span>
<span>{{ 'CONTACTS.ADD' | translate }}</span>
</div>
<button type="button" class="back-btn" (click)="back()">
<i class="icon back"></i>
<span>{{ 'COMMON.BACK' | translate }}</span>
</button>
</div>
<form class="form-add" [formGroup]="addContactForm" (ngSubmit)="add()">
<div class="input-block input-block-name">
<label for="add-name">{{ 'CONTACTS.FORM.NAME' | translate }}</label>
<input type="text" id="add-name" formControlName="name" (contextmenu)="variablesService.onContextMenu($event)">
<div class="error-block" *ngIf="addContactForm.controls['name'].invalid && (addContactForm.controls['name'].dirty || addContactForm.controls['name'].touched)">
<div *ngIf="addContactForm.controls['name'].errors['pattern']">
{{ 'CONTACTS.FORM_ERRORS.NAME_WRONG' | translate }}
</div>
<div *ngIf="addContactForm.get('name').value.length < 4 || addContactForm.get('name').value.length > 25">
{{ 'CONTACTS.FORM_ERRORS.NAME_LENGTH' | translate }}
</div>
<div *ngIf="addContactForm.controls['name'].errors['required']">
{{ 'CONTACTS.FORM_ERRORS.NAME_REQUIRED' | translate }}
</div>
<div *ngIf="addContactForm.controls['name'].errors['dublicated']">
{{ 'CONTACTS.FORM_ERRORS.NAME_DUBLICATED' | translate }}
</div>
</div>
</div>
<div class="input-block input-block-alias">
<label for="address">{{ 'CONTACTS.FORM.ADDRESS' | translate }}</label>
<input type="text" id="address" formControlName="address" (contextmenu)="variablesService.onContextMenu($event)">
<div class="error-block" *ngIf="addContactForm.controls['address'].invalid && (addContactForm.controls['address'].dirty || addContactForm.controls['address'].touched)">
<div *ngIf="addContactForm.controls['address'].errors['required']">
{{ 'CONTACTS.FORM_ERRORS.ADDRESS_REQUIRED' | translate }}
</div>
<div *ngIf="addContactForm.controls['address'].errors['address_not_valid']">
{{ 'CONTACTS.FORM_ERRORS.ADDRESS_NOT_VALID' | translate }}
</div>
<div *ngIf="addContactForm.controls['address'].errors['dublicated']">
{{ 'CONTACTS.FORM_ERRORS.ADDRESS_DUBLICATED' | translate }}
</div>
</div>
</div>
<div class="input-block input-block-notes">
<label for="notes">{{ 'CONTACTS.FORM.NOTES' | translate }}</label>
<input type="text" id="notes" formControlName="notes" (contextmenu)="variablesService.onContextMenu($event)">
<div class="error-block" *ngIf="addContactForm.controls['notes'].invalid">
<div *ngIf="addContactForm.controls['notes'].errors['maxLength']">
{{ 'CONTACTS.FORM_ERRORS.MAX_LENGTH' | translate }}
</div>
</div>
</div>
<button type="submit" class="blue-button" [disabled]="!addContactForm.valid">{{ 'CONTACTS.BUTTON.ADD_EDIT' | translate }}</button>
<app-send-modal *ngIf="isModalDialogVisible" [form]="addContactForm" (confirmed)="confirmed($event)"></app-send-modal>
</form>
</div>

View file

@ -0,0 +1,13 @@
.form-add {
margin-top: 3rem;
.input-block-name {
width: 50%;
}
button {
margin-top: 3rem;
width: 100%;
max-width: 18rem;
}
}

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddContactsComponent } from './add-contacts.component';
describe('AddContactsComponent', () => {
let component: AddContactsComponent;
let fixture: ComponentFixture<AddContactsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AddContactsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddContactsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,189 @@
import { Component, OnInit, NgZone, OnDestroy } 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 { ModalService } from '../_helpers/services/modal.service';
import { Location } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-add-contacts',
templateUrl: './add-contacts.component.html',
styleUrls: ['./add-contacts.component.scss']
})
export class AddContactsComponent implements OnInit, OnDestroy {
id: number;
queryRouting;
addContactForm = new FormGroup({
address: new FormControl('', [
Validators.required,
(g: FormControl) => {
if (g.value) {
this.backend.validateAddress(g.value, valid_status => {
this.ngZone.run(() => {
if (valid_status === false) {
g.setErrors(
Object.assign({ address_not_valid: true }, g.errors)
);
} else {
if (g.hasError('address_not_valid')) {
delete g.errors['address_not_valid'];
if (Object.keys(g.errors).length === 0) {
g.setErrors(null);
}
}
}
});
});
return g.hasError('address_not_valid')
? { address_not_valid: true }
: null;
}
return null;
},
(g: FormControl) => {
const isDublicated = this.variablesService.contacts.findIndex(
contact => contact.address === g.value
);
if (isDublicated !== -1 && !(this.id === isDublicated)) {
return { dublicated: true };
}
return null;
}
]),
notes: new FormControl('', [
(g: FormControl) => {
if (g.value) {
if (g.value.length > this.variablesService.maxCommentLength) {
return { maxLength: true };
} else {
return null;
}
} else {
return null;
}
}
]),
name: new FormControl('', [
Validators.required,
Validators.pattern(/^[\w\s-_.]{4,25}$/),
(g: FormControl) => {
if (g.value) {
const isDublicated = this.variablesService.contacts.findIndex(
contact => contact.name === g.value.trim()
);
if (isDublicated !== -1 && !(this.id === isDublicated)) {
return { dublicated: true };
}
return null;
}
}
])
});
constructor(
private route: ActivatedRoute,
private backend: BackendService,
public variablesService: VariablesService,
private modalService: ModalService,
private ngZone: NgZone,
private location: Location
) {}
ngOnInit() {
this.queryRouting = this.route.queryParams.subscribe(params => {
if (params.id) {
this.id = parseInt(params.id, 10);
this.addContactForm.reset({
name: this.variablesService.contacts[params.id]['name'],
address: this.variablesService.contacts[params.id]['address'],
notes: this.variablesService.contacts[params.id]['notes']
});
} else {
this.addContactForm.reset({
name: this.variablesService.newContact['name'],
address: this.variablesService.newContact['address'],
notes: this.variablesService.newContact['notes']
});
}
});
}
add() {
if (!this.variablesService.appPass) {
this.modalService.prepareModal(
'error',
'CONTACTS.FORM_ERRORS.SET_MASTER_PASSWORD'
);
} else {
if (this.addContactForm.valid) {
this.backend.validateAddress(
this.addContactForm.get('address').value,
valid_status => {
if (valid_status === false) {
this.ngZone.run(() => {
this.addContactForm
.get('address')
.setErrors({ address_not_valid: true });
});
} else {
if (this.id || this.id === 0) {
this.variablesService.contacts.forEach((contact, index) => {
if (index === this.id) {
contact.name = this.addContactForm.get('name').value.trim();
contact.address = this.addContactForm.get('address').value;
contact.notes =
this.addContactForm.get('notes').value || '';
}
});
this.backend.storeSecureAppData();
this.backend.getContactAlias();
this.modalService.prepareModal(
'success',
'CONTACTS.SUCCESS_SAVE'
);
} else {
this.variablesService.contacts.push({
name: this.addContactForm.get('name').value.trim(),
address: this.addContactForm.get('address').value,
notes: this.addContactForm.get('notes').value || ''
});
this.backend.storeSecureAppData();
this.backend.getContactAlias();
this.modalService.prepareModal(
'success',
'CONTACTS.SUCCESS_SENT'
);
this.variablesService.newContact = {
name: null,
address: null,
notes: null
};
this.addContactForm.reset({
name: null,
address: null,
notes: null
});
}
}
}
);
}
}
}
back() {
this.location.back();
}
ngOnDestroy() {
if (!(this.id || this.id === 0)) {
this.variablesService.newContact = {
name: this.addContactForm.get('name').value,
address: this.addContactForm.get('address').value,
notes: this.addContactForm.get('notes').value
};
}
this.queryRouting.unsubscribe();
}
}

View file

@ -20,8 +20,12 @@ import { RestoreWalletComponent } from './restore-wallet/restore-wallet.componen
import { SeedPhraseComponent } from './seed-phrase/seed-phrase.component';
import { WalletDetailsComponent } from './wallet-details/wallet-details.component';
import { AssignAliasComponent } from './assign-alias/assign-alias.component';
import { EditAliasComponent } from "./edit-alias/edit-alias.component";
import { TransferAliasComponent } from "./transfer-alias/transfer-alias.component";
import { EditAliasComponent } from './edit-alias/edit-alias.component';
import { TransferAliasComponent } from './transfer-alias/transfer-alias.component';
import { ContactsComponent } from './contacts/contacts.component';
import { AddContactsComponent } from './add-contacts/add-contacts.component';
import { ContactSendComponent } from './contact-send/contact-send.component';
import { ExportImportComponent } from './export-import/export-import.component';
const routes: Routes = [
{
@ -119,6 +123,26 @@ const routes: Routes = [
path: 'settings',
component: SettingsComponent
},
{
path: 'contacts',
component: ContactsComponent
},
{
path: 'add-contacts',
component: AddContactsComponent
},
{
path: 'edit-contacts/:id',
component: AddContactsComponent
},
{
path: 'contact-send/:id',
component: ContactSendComponent
},
{
path: 'import',
component: ExportImportComponent
},
{
path: '',
redirectTo: '/',

View file

@ -193,6 +193,7 @@ export class AppComponent implements OnInit, OnDestroy {
});
if (!this.firstOnlineState && data['daemon_network_state'] === 2) {
this.getAliases();
this.backend.getContactAlias();
this.backend.getDefaultFee((status_fee, data_fee) => {
this.variablesService.default_fee_big = new BigNumber(data_fee);
this.variablesService.default_fee = this.intToMoneyPipe.transform(data_fee);

View file

@ -51,12 +51,17 @@ import * as highcharts from 'highcharts';
import exporting from 'highcharts/modules/exporting.src';
import { ProgressContainerComponent } from './_helpers/directives/progress-container/progress-container.component';
import { InputDisableSelectionDirective } from './_helpers/directives/input-disable-selection/input-disable-selection.directive';
import { SendModalComponent } from './send-modal/send-modal.component';
import { ContactsComponent } from './contacts/contacts.component';
import { AddContactsComponent } from './add-contacts/add-contacts.component';
import { ContactSendComponent } from './contact-send/contact-send.component';
import { ExportImportComponent } from './export-import/export-import.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, './assets/i18n/', '.json');
}
import { PapaParseModule } from 'ngx-papaparse';
// import * as more from 'highcharts/highcharts-more.src';
// import * as exporting from 'highcharts/modules/exporting.src';
// import * as highstock from 'highcharts/modules/stock.src';
@ -108,7 +113,12 @@ export function highchartsFactory() {
ModalContainerComponent,
TransactionDetailsComponent,
ProgressContainerComponent,
InputDisableSelectionDirective
InputDisableSelectionDirective,
SendModalComponent,
ContactsComponent,
AddContactsComponent,
ContactSendComponent,
ExportImportComponent
],
imports: [
BrowserModule,
@ -125,6 +135,7 @@ export function highchartsFactory() {
ReactiveFormsModule,
NgSelectModule,
ChartModule,
PapaParseModule,
ContextMenuModule.forRoot()
],
providers: [
@ -136,7 +147,8 @@ export function highchartsFactory() {
// {provide: HIGHCHARTS_MODULES, useFactory: () => [ highstock, more, exporting ] }
],
entryComponents: [
ModalContainerComponent
ModalContainerComponent,
SendModalComponent
],
bootstrap: [AppComponent]
})

View file

@ -61,7 +61,6 @@
<div class="wrap-buttons">
<button type="button" class="blue-button" (click)="assignAlias()" [disabled]="!assignForm.valid || !canRegister || notEnoughMoney">{{ 'ASSIGN_ALIAS.BUTTON_ASSIGN' | translate }}</button>
<button type="button" class="blue-button" (click)="back()">{{ 'ASSIGN_ALIAS.BUTTON_CANCEL' | translate }}</button>
</div>
</form>

View file

@ -0,0 +1,44 @@
<div class="content scrolled-content">
<div class="head">
<div class="breadcrumbs">
<span [routerLink]="['/contacts']">{{
'CONTACTS.TITLE' | translate
}}</span>
<span>{{ 'CONTACTS.SEND' | translate }}</span>
</div>
<button type="button" class="back-btn" (click)="back()">
<i class="icon back"></i>
<span>{{ 'COMMON.BACK' | translate }}</span>
</button>
</div>
<div>
<div class="wallets-selection">
<div class="input-block">
<label>
{{ 'CONTACTS.SEND_FROM' | translate }}
</label>
<ng-select
class="custom-select"
[items]="this.variablesService.wallets"
[(ngModel)]="this.variablesService.selectWallet"
bindValue="wallet_id"
bindLabel="name"
[clearable]="false"
[searchable]="false"
>
</ng-select>
</div>
<button [routerLink]="['/main']">
{{ 'CONTACTS.OPEN_ADD_WALLET' | translate }}
</button>
</div>
<div class="input-block">
<label for="address">{{ 'CONTACTS.SEND_TO' | translate }}</label>
<input type="text" id="address" [ngModel]="address" [readonly]="true"/>
</div>
</div>
<button class="blue-button" [routerLink]="['/wallet/' + this.variablesService.selectWallet + '/send']" [queryParams]="{send: true}" (click)="goToWallet(this.variablesService.selectWallet)"
[disabled]="!(this.variablesService.selectWallet === 0 || this.variablesService.selectWallet)">{{ 'CONTACTS.BUTTON.GO_TO_WALLET' | translate }}</button>
</div>

View file

@ -0,0 +1,33 @@
.wallets-selection {
display: flex;
align-items: center;
margin-top: 2rem;
.input-block {
width: 18rem;
}
button {
padding: 2rem;
background: transparent;
border: none;
outline: none;
}
}
.input-block {
width: 44rem;
input {
overflow: hidden;
text-overflow: ellipsis;
}
}
.blue-button {
margin-top: 2.5rem;
width: 100%;
max-width: 18rem;
}

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactSendComponent } from './contact-send.component';
describe('ContactSendComponent', () => {
let component: ContactSendComponent;
let fixture: ComponentFixture<ContactSendComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ContactSendComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContactSendComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,44 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Location } from '@angular/common';
import { VariablesService } from '../_helpers/services/variables.service';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-contact-send',
templateUrl: './contact-send.component.html',
styleUrls: ['./contact-send.component.scss']
})
export class ContactSendComponent implements OnInit, OnDestroy {
queryRouting;
address;
constructor(
private location: Location,
private variablesService: VariablesService,
private route: ActivatedRoute
) { }
ngOnInit() {
this.queryRouting = this.route.queryParams.subscribe(params => {
if (params.address) {
this.address = params.address;
}
});
}
goToWallet(id) {
this.variablesService.setCurrentWallet(id);
this.variablesService.currentWallet.send_data['address'] = this.address;
}
back() {
this.location.back();
}
ngOnDestroy() {
this.queryRouting.unsubscribe();
}
}

View file

@ -0,0 +1,98 @@
<div class="content scrolled-content">
<div>
<div class="head">
<button type="button" class="back-btn" (click)="back()">
<i class="icon back"></i>
<span>{{ 'COMMON.BACK' | translate }}</span>
</button>
</div>
<h3 class="contacts-title">{{ 'CONTACTS.TITLE' | translate }}</h3>
<div class="wrap-table">
<ng-container>
<table
*ngIf="this.variablesService.contacts.length !== 0; else emptyList"
>
<thead>
<tr #head (window:resize)="calculateWidth()">
<th>{{ 'CONTACTS.TABLE.NAME' | translate }}</th>
<th>{{ 'CONTACTS.TABLE.ALIAS' | translate }}</th>
<th>{{ 'CONTACTS.TABLE.ADDRESS' | translate }}</th>
<th>{{ 'CONTACTS.TABLE.NOTES' | translate }}</th>
<th></th>
</tr>
</thead>
<tbody>
<ng-container
*ngFor="
let contact of this.variablesService.contacts;
let i = index
"
>
<tr>
<td>
{{ contact.name }}
</td>
<td>
<ng-container *ngIf="contact.alias">
<span
class="alias"
(click)="openInBrowser(contact.alias)"
>{{ contact.alias }}</span
>
</ng-container>
</td>
<td class="remote-address">
{{ contact.address }}
</td>
<td class="remote-notes">
{{ contact.notes }}
</td>
<td>
<div class="button-wrapper">
<button
[routerLink]="['/contact-send/' + i]"
[queryParams]="{ address: contact.address }"
>
<i class="icon transfer"></i>
<span>{{ 'CONTACTS.BUTTON.SEND' | translate }}</span>
</button>
<button
[routerLink]="['/edit-contacts/' + i]"
[queryParams]="{ id: i }"
>
<i class="icon edit"></i>
<span>{{ 'CONTACTS.BUTTON.EDIT' | translate }}</span>
</button>
<button (click)="delete(i)">
<i class="icon delete"></i>
<span>{{ 'CONTACTS.BUTTON.DELETE' | translate }}</span>
</button>
</div>
</td>
</tr>
</ng-container>
</tbody>
</table>
</ng-container>
<ng-template #emptyList>
<div class="empty-list">
{{ 'CONTACTS.TABLE.EMPTY' | translate }}
</div>
</ng-template>
</div>
<button [routerLink]="['/add-contacts']" class="blue-button">
{{ 'CONTACTS.BUTTON.ADD' | translate }}
</button>
<div class="footer">
<button type="button" class="import-btn" [routerLink]="['/import']">
<i class="icon import"></i>
<span>{{ 'CONTACTS.BUTTON.IMPORT_EXPORT' | translate }}</span>
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,117 @@
:host {
min-width: 95rem;
width: 100%;
height: 100%;
}
.head {
justify-content: flex-end;
}
.contacts-title {
font-size: 1.7rem;
}
.wrap-table {
margin: 1rem -3rem;
table {
tbody{
tr {
td {
padding: 0 3rem 0 1rem;
overflow: hidden;
text-overflow: ellipsis;
&:first-child {
max-width: 10rem;
padding: 0 3rem 0 3rem;
}
&:nth-child(2) {
max-width: 10rem;
}
.alias {
cursor: pointer;
}
.button-wrapper {
display: flex;
button {
display: flex;
align-items: center;
background: transparent;
border: none;
font-size: 1.3rem;
font-weight: 400;
line-height: 3rem;
outline: none;
padding: 0;
height: auto;
margin-right: 1.8rem;
.icon {
cursor: pointer;
margin-right: 0.8rem;
width: 1.7rem;
height: 1.7rem;
&.edit {
mask: url(../../assets/icons/edit.svg) no-repeat center;
}
&.transfer {
mask: url(../../assets/icons/send.svg) no-repeat center;
}
&.delete {
mask: url(../../assets/icons/delete.svg) no-repeat center;
}
}
}
}
}
}
}
}
.empty-list {
margin: 2.5rem 3rem;
}
}
.blue-button {
width: 100%;
max-width: 18rem;
margin-top: 3rem;
}
.footer {
position: absolute;
bottom: 3rem;
font-size: 1.3rem;
.import-btn {
display: flex;
align-items: center;
background-color: transparent;
font-size: inherit;
font-weight: 400;
line-height: 1.3rem;
padding: 0;
height: auto;
.icon {
margin-right: 0.7rem;
mask: url(../../assets/icons/import-export.svg) no-repeat center;
width: 0.9rem;
height: 0.9rem;
}
}
}

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactsComponent } from './contacts.component';
describe('ContactsComponent', () => {
let component: ContactsComponent;
let fixture: ComponentFixture<ContactsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ContactsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContactsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,60 @@
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { Location } from '@angular/common';
import { VariablesService } from '../_helpers/services/variables.service';
import { BackendService } from '../_helpers/services/backend.service';
@Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.scss']
})
export class ContactsComponent implements OnInit {
calculatedWidth = [];
@ViewChild('head') head: ElementRef;
constructor(
private location: Location,
private variablesService: VariablesService,
private backend: BackendService
) {}
ngOnInit() {
this.backend.getContactAlias();
}
delete(index: number) {
if (this.variablesService.appPass) {
this.variablesService.contacts.splice(index, 1);
this.backend.storeSecureAppData();
}
}
calculateWidth() {
this.calculatedWidth = [];
this.calculatedWidth.push(
this.head.nativeElement.childNodes[0].clientWidth
);
this.calculatedWidth.push(
this.head.nativeElement.childNodes[1].clientWidth +
this.head.nativeElement.childNodes[2].clientWidth
);
this.calculatedWidth.push(
this.head.nativeElement.childNodes[3].clientWidth
);
this.calculatedWidth.push(
this.head.nativeElement.childNodes[4].clientWidth
);
}
openInBrowser(alias: string) {
if (alias !== null) {
this.backend.openUrlInBrowser(
`explorer.zano.org/aliases/${alias.slice(1)}`
);
}
}
back() {
this.location.back();
}
}

View file

@ -44,7 +44,6 @@
<div class="wrap-buttons">
<button type="button" class="blue-button" (click)="updateAlias()" [disabled]="notEnoughMoney || (oldAliasComment === alias.comment) || alias.comment.length > variablesService.maxCommentLength">{{ 'EDIT_ALIAS.BUTTON_EDIT' | translate }}</button>
<button type="button" class="blue-button" (click)="back()">{{ 'EDIT_ALIAS.BUTTON_CANCEL' | translate }}</button>
</div>
</form>

View file

@ -0,0 +1,21 @@
<div class="content scrolled-content">
<div>
<div class="head">
<button type="button" class="back-btn" (click)="back()">
<i class="icon back"></i>
<span>{{ 'COMMON.BACK' | translate }}</span>
</button>
</div>
<h3 class="contacts-title">{{ 'CONTACTS.IMPORT_EXPORT' | translate }}</h3>
<div class="btn-wrapper">
<button class="blue-button" type="button" (click)="import()">
{{ 'CONTACTS.IMPORT' | translate }}
</button>
<button class="blue-button" type="button" (click)="export()">
{{ 'CONTACTS.EXPORT' | translate }}
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,25 @@
:host {
width: 100%;
}
.head {
justify-content: flex-end;
}
.contacts-title {
font-size: 1.7rem;
margin-bottom: 1rem;
}
.btn-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 -0.5rem;
padding: 1.5rem 0;
button {
flex: 1 0 auto;
margin: 0 0.5rem;
}
}

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ExportImportComponent } from './export-import.component';
describe('ExportImportComponent', () => {
let component: ExportImportComponent;
let fixture: ComponentFixture<ExportImportComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ExportImportComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ExportImportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,124 @@
import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { BackendService } from '../_helpers/services/backend.service';
import { VariablesService } from '../_helpers/services/variables.service';
import { Contact } from '../_helpers/models/contact.model';
import { ModalService } from '../_helpers/services/modal.service';
import { Papa } from 'ngx-papaparse';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-export-import',
templateUrl: './export-import.component.html',
styleUrls: ['./export-import.component.scss']
})
export class ExportImportComponent implements OnInit {
csvContent;
constructor(
private location: Location,
private variablesService: VariablesService,
private backend: BackendService,
private modalService: ModalService,
private papa: Papa,
private translate: TranslateService
) {}
ngOnInit() {}
import() {
this.backend.openFileDialog(
'',
'*',
this.variablesService.settings.default_path,
(file_status, file_data) => {
if (file_status) {
this.variablesService.settings.default_path = file_data.path.substr(
0,
file_data.path.lastIndexOf('/')
);
if (this.isValid(file_data.path)) {
this.backend.loadFile(file_data.path, (status, data) => {
if (status) {
const options = {
header: true
};
const elements = this.papa.parse(data, options);
if (elements.data && !elements.errors.length) {
if (!this.variablesService.contacts.length) {
elements.data.forEach(element => {
this.variablesService.contacts.push(element);
});
} else {
elements.data.forEach(element => {
const indexName = this.variablesService.contacts.findIndex(
contact => contact.name === element.name
);
const indexAddress = this.variablesService.contacts.findIndex(
contact => contact.address === element.address
);
if (indexAddress === -1 && indexName === -1) {
this.variablesService.contacts.push(element);
}
if (indexName !== -1 && indexAddress === -1) {
this.variablesService.contacts.push({
name: `${element.name} ${this.translate.instant(
'CONTACTS.COPY'
)}`,
address: element.address,
notes: element.notes
});
}
});
}
this.backend.getContactAlias();
this.modalService.prepareModal(
'success',
'CONTACTS.SUCCESS_IMPORT'
);
}
if (elements.errors.length) {
this.modalService.prepareModal(
'error',
'CONTACTS.ERROR_IMPORT'
);
console.log(elements.errors);
}
}
});
} else {
this.modalService.prepareModal('error', 'CONTACTS.ERROR_TYPE_FILE');
}
}
}
);
}
export() {
const contacts: Array<Contact> = [];
this.variablesService.contacts.forEach(contact => {
delete contact.alias;
contacts.push(contact);
});
this.backend.saveFileDialog(
'',
'*',
this.variablesService.settings.default_path,
(file_status, file_data) => {
if (file_status) {
this.backend.storeFile(file_data.path, this.papa.unparse(contacts));
}
}
);
}
isValid(file) {
return file.endsWith('.csv');
}
back() {
this.location.back();
}
}

View file

@ -13,16 +13,33 @@
<tbody>
<ng-container *ngFor="let item of variablesService.currentWallet.history">
<tr (click)="openDetails(item.tx_hash)" [class.locked-transaction]="!item.is_mining && item.unlock_time > 0">
<td>
<td>
<div class="status" [class.send]="!item.is_income" [class.received]="item.is_income">
<ng-container *ngIf="variablesService.height_app - item.height < 10 || item.height === 0 && item.timestamp > 0">
<div class="confirmation" tooltip="{{ 'HISTORY.STATUS_TOOLTIP' | translate : {'current': getHeight(item)/10, 'total': 10} }}" placement="bottom-left" tooltipClass="table-tooltip" [delay]="500">
<div class="fill" [style.height]="getHeight(item) + '%'"></div>
</div>
</ng-container>
<ng-container *ngIf="!item.is_mining && item.unlock_time > 0">
<i class="icon lock-transaction" tooltip="{{ 'HISTORY.LOCK_TOOLTIP' | translate : {'date': item.unlock_time * 1000 | date : 'MM.dd.yy'} }}" placement="bottom-left" tooltipClass="table-tooltip" [delay]="500"></i>
<ng-container *ngIf="item.unlock_time !== 0 && item.tx_type !== 6">
<ng-container *ngIf="isLocked(item); else unlock">
<ng-container *ngIf="item.unlock_time < 500000000">
<i class="icon lock-transaction" tooltip="{{ 'HISTORY.LOCK_TOOLTIP' | translate : {'date': time(item) | date : 'MM.dd.yy'} }}" placement="bottom-left" tooltipClass="table-tooltip" [delay]="500"
[class.position]="variablesService.height_app - item.height < 10 || item.height === 0 && item.timestamp > 0"></i>
</ng-container>
<ng-container *ngIf="item.unlock_time > 500000000">
<i class="icon lock-transaction" tooltip="{{ 'HISTORY.LOCK_TOOLTIP' | translate : {'date': item.unlock_time * 1000 | date : 'MM.dd.yy'} }}" placement="bottom-left" tooltipClass="table-tooltip" [delay]="500"
[class.position]="variablesService.height_app - item.height < 10 || item.height === 0 && item.timestamp > 0"></i>
</ng-container>
</ng-container>
<ng-template #unlock>
<i class="icon unlock-transaction" placement="bottom-left" [class.position]="variablesService.height_app - item.height < 10 || item.height === 0 && item.timestamp > 0"></i>
</ng-template>
</ng-container>
<!-- <ng-container *ngIf="!item.is_mining && item.unlock_time > 0">
<i class="icon lock-transaction" tooltip="{{ 'HISTORY.LOCK_TOOLTIP' | translate : {'date': item.unlock_time * 1000 | date : 'MM.dd.yy'} }}" placement="bottom-left" tooltipClass="table-tooltip" [delay]="500"></i>
</ng-container> -->
<i class="icon status-transaction"></i>
<span>{{ (item.is_income ? 'HISTORY.RECEIVED' : 'HISTORY.SEND') | translate }}</span>
</div>

View file

@ -37,12 +37,24 @@
.lock-transaction {
position: absolute;
top: 50%;
left: -2rem;
transform: translateY(-50%);
mask: url(../../assets/icons/lock-transaction.svg) no-repeat center;
width: 1.2rem;
height: 1.2rem;
margin-right: 1.1rem;
}
.unlock-transaction {
position: absolute;
left: -2rem;
mask: url(../../assets/icons/unlock-transaction.svg) no-repeat center;
width: 1.2rem;
height: 1.2rem;
margin-right: 1.1rem;
}
.position {
position: static;
}
.status-transaction {

View file

@ -1,6 +1,7 @@
import {Component, OnInit, OnDestroy, AfterViewChecked, ViewChild, ElementRef} from '@angular/core';
import {VariablesService} from '../_helpers/services/variables.service';
import {ActivatedRoute} from '@angular/router';
import { Transaction } from '../_helpers/models/transaction.model';
@Component({
selector: 'app-history',
@ -56,6 +57,23 @@ export class HistoryComponent implements OnInit, OnDestroy, AfterViewChecked {
this.calculatedWidth.push(this.head.nativeElement.childNodes[4].clientWidth);
}
time(item: Transaction) {
const now = new Date().getTime();
const unlockTime = now + ((item.unlock_time - this.variablesService.height_app) * 60 * 1000);
return unlockTime;
}
isLocked(item: Transaction) {
if ((item.unlock_time > 500000000) && (item.unlock_time > new Date().getTime() / 1000)) {
console.log(new Date().getTime());
return true;
}
if ((item.unlock_time < 500000000) && (item.unlock_time > this.variablesService.height_app)) {
return true;
}
return false;
}
ngOnDestroy() {
this.parentRouting.unsubscribe();
}

View file

@ -26,7 +26,7 @@ export class LoginComponent implements OnInit, OnDestroy {
password: new FormControl('')
});
type = 'reg';
type = 'reg';
constructor(
private route: ActivatedRoute,
@ -61,8 +61,7 @@ export class LoginComponent implements OnInit, OnDestroy {
} else {
console.log(data['error_code']);
}
})
});
}
}
@ -74,10 +73,12 @@ export class LoginComponent implements OnInit, OnDestroy {
});
}
dropSecureAppData(): void{
dropSecureAppData(): void {
this.backend.dropSecureAppData(() => {
this.onSkipCreatePass();
});
this.onSkipCreatePass();
});
this.variablesService.wallets = [];
this.variablesService.contacts = [];
}
onSubmitAuthPass(): void {
@ -93,9 +94,9 @@ export class LoginComponent implements OnInit, OnDestroy {
this.router.navigate(['/']);
});
}
})
});
} else {
this.getWalletData(this.variablesService.appPass)
this.getWalletData(this.variablesService.appPass);
}
}
}
@ -107,16 +108,21 @@ export class LoginComponent implements OnInit, OnDestroy {
this.variablesService.dataIsLoaded = true;
this.variablesService.startCountdown();
this.variablesService.appPass = appPass;
if (Object.keys(data['contacts']).length !== 0) {
data['contacts'].map(contact => {
this.variablesService.contacts.push(contact);
});
}
if (this.variablesService.wallets.length) {
this.ngZone.run(() => {
this.router.navigate(['/wallet/' + this.variablesService.wallets[0].wallet_id]);
});
return;
}
if (Object.keys(data).length !== 0) {
if (Object.keys(data['wallets']).length !== 0) {
let openWallets = 0;
let runWallets = 0;
data.forEach((wallet, wallet_index) => {
data['wallets'].forEach((wallet, wallet_index) => {
this.backend.openWallet(wallet.path, wallet.pass, true, (open_status, open_data, open_error) => {
if (open_status || open_error === 'FILE_RESTORED') {
openWallets++;

View file

@ -21,8 +21,7 @@
<div class="wrap-buttons">
<button type="button" class="blue-button seed-phrase-button" (click)="runWallet()">{{ 'SEED_PHRASE.BUTTON_CREATE_ACCOUNT' | translate }}</button>
<button type="button" class="blue-button copy-button" *ngIf="!seedPhraseCopied" (click)="copySeedPhrase()">{{ 'SEED_PHRASE.BUTTON_COPY' | translate }}</button>
<button type="button" class="transparent-button copy-button" *ngIf="seedPhraseCopied" disabled><i class="icon"></i>{{ 'SEED_PHRASE.BUTTON_COPY' | translate }}</button>
<button type="button" class="blue-button copy-button" (click)="copySeedPhrase()">{{ 'SEED_PHRASE.BUTTON_COPY' | translate }}</button>
</div>
</div>

View file

@ -0,0 +1,31 @@
<div class="modal">
<div class="title">
<span>{{ 'CONFIRM.TITLE' | translate }}</span>
</div>
<div class="content">
<div class="message-container">
<div class="message-block">
<div class="message-label">{{ 'CONFIRM.MESSAGE.SEND' | translate }}</div>
<div class="message-text">{{ form.get('amount').value }} {{variablesService.defaultCurrency}}</div>
</div>
<div class="message-block">
<div class="message-label">{{ 'CONFIRM.MESSAGE.FROM' | translate }}</div>
<div class="message-text">{{ variablesService.currentWallet.address }}</div>
</div>
<div class="message-block">
<div class="message-label">{{ 'CONFIRM.MESSAGE.TO' | translate }}</div>
<div class="message-text">{{ form.get('address').value }}</div>
</div>
<ng-container *ngIf="form.get('comment').value != ''">
<div class="message-block" *ngIf="form.get('comment').value != null">
<div class="message-label">{{ 'CONFIRM.MESSAGE.COMMENT' | translate }}</div>
<div class="message-text">{{ form.get('comment').value }}</div>
</div>
</ng-container>
</div>
</div>
<div class="wrapper-buttons">
<button type="button" class="blue-button" (click)="confirm()">{{ 'CONFIRM.BUTTON_CONFIRM' | translate }}</button>
<button type="button" class="blue-button" (click)="onClose()">{{ 'CONFIRM.BUTTON_CANCEL' | translate }}</button>
</div>
</div>

View file

@ -0,0 +1,84 @@
:host {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.25);
}
.modal {
position: relative;
display: flex;
flex-direction: column;
background-position: center;
background-size: 200%;
padding: 0.3rem 3rem 3rem 3rem;
width: 64rem;
.title {
padding: 1.4rem 0;
font-size: 1.8rem;
line-height: 3rem;
}
.content {
display: flex;
font-size: 1.4rem;
.message-container {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin: 1.4rem 3rem 6.2rem 0;
.message-block {
display: flex;
margin-bottom: 1rem;
&:first-child {
.message-label {
line-height: 4rem;
}
.message-text {
line-height: 4rem;
}
}
&:last-child {
margin-bottom: 0;
}
.message-label {
min-width: 6.7rem;
line-height: 2rem;
}
.message-text {
overflow-wrap: break-word;
margin-left: 4.8rem;
width: 43.4rem;
line-height: 2rem;
}
}
}
}
.wrapper-buttons {
display: flex;
align-items: center;
justify-content: space-between;
button {
width: 100%;
max-width: 15rem;
}
}
}

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SendModalComponent } from './send-modal.component';
describe('SendModalComponent', () => {
let component: SendModalComponent;
let fixture: ComponentFixture<SendModalComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SendModalComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SendModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,32 @@
import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { VariablesService } from '../_helpers/services/variables.service';
@Component({
selector: 'app-send-modal',
templateUrl: './send-modal.component.html',
styleUrls: ['./send-modal.component.scss']
})
export class SendModalComponent implements OnInit {
@Input() form: FormGroup;
@Output() confirmed: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor(
public variablesService: VariablesService
) {
}
ngOnInit() {
}
confirm() {
this.confirmed.emit(true);
}
onClose() {
this.confirmed.emit(false);
}
}

View file

@ -1,4 +1,4 @@
<form class="form-send" [formGroup]="sendForm" (ngSubmit)="onSend()">
<form class="form-send" [formGroup]="sendForm" (ngSubmit)="showDialog()">
<div class="input-block input-block-alias">
<label for="send-address">{{ 'SEND.ADDRESS' | translate }}</label>
@ -85,4 +85,6 @@
<button type="submit" class="blue-button" [disabled]="!sendForm.valid || !variablesService.currentWallet.loaded">{{ 'SEND.BUTTON' | translate }}</button>
<app-send-modal *ngIf="isModalDialogVisible" [form]="sendForm" (confirmed)="confirmed($event)"></app-send-modal>
</form>

View file

@ -1,4 +1,4 @@
import {Component, OnInit, OnDestroy, NgZone, HostListener} from '@angular/core';
import {Component, OnInit, OnDestroy, NgZone, HostListener, Input} from '@angular/core';
import {FormGroup, FormControl, Validators} from '@angular/forms';
import {ActivatedRoute} from '@angular/router';
import {BackendService} from '../_helpers/services/backend.service';
@ -15,6 +15,7 @@ export class SendComponent implements OnInit, OnDestroy {
isOpen = false;
localAliases = [];
isModalDialogVisible = false;
currentWalletId = null;
parentRouting;
@ -73,13 +74,7 @@ export class SendComponent implements OnInit, OnDestroy {
}
return null;
}]),
comment: new FormControl('', [(g: FormControl) => {
if (g.value > this.variablesService.maxCommentLength) {
return {'maxLength': true};
} else {
return null;
}
}]),
comment: new FormControl(''),
mixin: new FormControl(0, Validators.required),
fee: new FormControl(this.variablesService.default_fee, [Validators.required, (g: FormControl) => {
if ((new BigNumber(g.value)).isLessThan(this.variablesService.default_fee)) {
@ -132,6 +127,17 @@ export class SendComponent implements OnInit, OnDestroy {
});
}
showDialog() {
this.isModalDialogVisible = true;
}
confirmed(confirmed: boolean) {
if (confirmed) {
this.onSend();
}
this.isModalDialogVisible = false;
}
onSend() {
if (this.sendForm.valid) {
if (this.sendForm.get('address').value.indexOf('@') !== 0) {

View file

@ -38,6 +38,12 @@
</div>
</div>
<div class="sidebar-settings">
<div class="wrap-button" routerLinkActive="active">
<button (click)="contactsRoute()" [class.disabled]="variablesService.daemon_state !== 2" [disabled]="variablesService.daemon_state !== 2">
<i class="icon contacts"></i>
<span>{{ 'SIDEBAR.CONTACTS' | translate }}</span>
</button>
</div>
<div class="wrap-button" routerLinkActive="active">
<button [routerLink]="['/settings']">
<i class="icon settings"></i>

View file

@ -198,11 +198,19 @@
padding: 0 3rem;
width: 100%;
&.disabled {
cursor: url(../../assets/icons/not-allowed.svg), not-allowed;
}
.icon {
margin-right: 1.2rem;
width: 1.7rem;
height: 1.7rem;
&.contacts {
mask: url(../../assets/icons/contacts.svg) no-repeat center;
}
&.settings {
mask: url(../../assets/icons/settings.svg) no-repeat center;
}

View file

@ -2,6 +2,7 @@ import {Component, NgZone, OnInit, OnDestroy} from '@angular/core';
import {ActivatedRoute, NavigationStart, Router} from '@angular/router';
import {VariablesService} from '../_helpers/services/variables.service';
import {BackendService} from '../_helpers/services/backend.service';
import { ModalService } from '../_helpers/services/modal.service';
@Component({
selector: 'app-sidebar',
@ -18,6 +19,7 @@ export class SidebarComponent implements OnInit, OnDestroy {
private router: Router,
public variablesService: VariablesService,
private backend: BackendService,
private modal: ModalService,
private ngZone: NgZone
) {}
@ -49,6 +51,17 @@ export class SidebarComponent implements OnInit, OnDestroy {
});
}
contactsRoute() {
if (this.variablesService.appPass) {
this.router.navigate(['/contacts']);
} else {
this.modal.prepareModal(
'error',
'CONTACTS.FORM_ERRORS.SET_MASTER_PASSWORD'
);
}
}
getUpdate() {
this.backend.openUrlInBrowser('zano.org/downloads.html');
}

View file

@ -15,14 +15,14 @@
<div class="input-block alias-name">
<label for="alias-name">
{{ 'EDIT_ALIAS.NAME.LABEL' | translate }}
{{ 'TRANSFER_ALIAS.NAME.LABEL' | translate }}
</label>
<input type="text" id="alias-name" [value]="alias.name" placeholder="{{ 'EDIT_ALIAS.NAME.PLACEHOLDER' | translate }}" readonly>
</div>
<div class="input-block textarea">
<label for="alias-comment">
{{ 'EDIT_ALIAS.COMMENT.LABEL' | translate }}
{{ 'TRANSFER_ALIAS.COMMENT.LABEL' | translate }}
</label>
<textarea id="alias-comment" [value]="alias.comment" placeholder="{{ 'EDIT_ALIAS.COMMENT.PLACEHOLDER' | translate }}" readonly></textarea>
</div>
@ -49,7 +49,6 @@
<div class="wrap-buttons">
<button type="button" class="blue-button" (click)="transferAlias()" [disabled]="transferAddressAlias || !transferAddressValid || notEnoughMoney">{{ 'TRANSFER_ALIAS.BUTTON_TRANSFER' | translate }}</button>
<button type="button" class="blue-button" (click)="back()">{{ 'TRANSFER_ALIAS.BUTTON_CANCEL' | translate }}</button>
</div>
</form>

View file

@ -14,6 +14,7 @@ import {Subscription} from 'rxjs';
export class WalletComponent implements OnInit, OnDestroy {
subRouting1;
subRouting2;
queryRouting;
walletID;
copyAnimation = false;
copyAnimationTimeout;
@ -94,6 +95,15 @@ export class WalletComponent implements OnInit, OnDestroy {
}
}
});
this.queryRouting = this.route.queryParams.subscribe(params => {
if (params.send) {
this.tabs.forEach((tab, index) => {
if (tab.link === '/send') {
this.changeTab(index);
}
});
}
});
if (this.variablesService.currentWallet.alias.hasOwnProperty('name')) {
this.variablesService.currentWallet.wakeAlias = false;
}
@ -178,6 +188,7 @@ export class WalletComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.subRouting1.unsubscribe();
this.subRouting2.unsubscribe();
this.queryRouting.unsubscribe();
this.aliasSubscription.unsubscribe();
clearTimeout(this.copyAnimationTimeout);
}

View file

@ -38,6 +38,7 @@
"MESSAGES": "New offers/Messages",
"SYNCING": "Syncing wallet"
},
"CONTACTS": "Contacts",
"SETTINGS": "Settings",
"LOG_OUT": "Log out",
"SYNCHRONIZATION": {
@ -192,18 +193,17 @@
},
"ASSIGN_ALIAS": {
"NAME": {
"LABEL": "Unique name",
"LABEL": "Alias",
"PLACEHOLDER": "@ Enter alias",
"TOOLTIP": "An alias is a shortened form or your account. An alias can only include Latin letters, numbers and characters “.” and “-”. It must start with “@”."
},
"COMMENT": {
"LABEL": "Comment",
"PLACEHOLDER": "Enter comment",
"PLACEHOLDER": "",
"TOOLTIP": "The comment will be visible to anyone who wants to make a payment to your alias. You can provide details about your business, contacts, or include any text. Comments can be edited later."
},
"COST": "Cost to create alias {{value}} {{currency}}",
"COST": "Alias fee {{value}} {{currency}}",
"BUTTON_ASSIGN": "Assign",
"BUTTON_CANCEL": "Cancel",
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_WRONG": "Alias has wrong name",
@ -217,40 +217,39 @@
},
"EDIT_ALIAS": {
"NAME": {
"LABEL": "Unique name",
"LABEL": "Alias",
"PLACEHOLDER": "@ Enter alias"
},
"COMMENT": {
"LABEL": "Comment",
"PLACEHOLDER": "Enter comment"
"PLACEHOLDER": ""
},
"FORM_ERRORS": {
"NO_MONEY": "You do not have enough funds to change the comment to this alias",
"MAX_LENGTH": "Maximum comment length reached"
},
"COST": "Cost to edit alias {{value}} {{currency}}",
"BUTTON_EDIT": "Edit",
"BUTTON_CANCEL": "Cancel"
"COST": "Fee {{value}} {{currency}}",
"BUTTON_EDIT": "Edit"
},
"TRANSFER_ALIAS": {
"NAME": {
"LABEL": "Unique name",
"LABEL": "Alias",
"PLACEHOLDER": "@ Enter alias"
},
"COMMENT": {
"LABEL": "Comment",
"PLACEHOLDER": "Enter comment"
"PLACEHOLDER": ""
},
"ADDRESS": {
"LABEL": "The account to which the alias will be transferred",
"PLACEHOLDER": "Enter wallet address"
"LABEL": "Transfer to",
"PLACEHOLDER": ""
},
"FORM_ERRORS": {
"WRONG_ADDRESS": "No wallet with this account exists",
"ALIAS_EXISTS": "This account already has an alias",
"NO_MONEY": "You do not have enough funds to transfer this alias"
},
"COST": "Cost to transfer alias {{value}} {{currency}}",
"COST": "Transfer fee {{value}} {{currency}}",
"BUTTON_TRANSFER": "Transfer",
"BUTTON_CANCEL": "Cancel",
"REQUEST_SEND_REG": "The alias will be transferred within 10 minutes"
@ -453,6 +452,17 @@
"INFO": "Information",
"OK": "OK"
},
"CONFIRM": {
"BUTTON_CONFIRM": "Send",
"BUTTON_CANCEL": "Cancel",
"TITLE": "Confirm transaction",
"MESSAGE": {
"SEND": "Send",
"FROM": "From",
"TO": "To",
"COMMENT": "Comment"
}
},
"STAKING": {
"TITLE": "Staking",
"TITLE_PENDING": "Pending",
@ -478,6 +488,55 @@
"OFF": "OFF"
}
},
"CONTACTS": {
"TITLE": "Contact list",
"IMPORT_EXPORT": "Import or export contacts",
"IMPORT": "Import",
"EXPORT": "Export",
"ADD": "Add/edit contact",
"SEND": "Send",
"SEND_FROM": "Send from",
"SEND_TO": "To",
"OPEN_ADD_WALLET": "Open/Add wallet",
"COPY": "- Copy"
, "TABLE": {
"NAME": "Name",
"ALIAS": "Alias",
"ADDRESS": "Address",
"NOTES": "Notes",
"EMPTY": "Contact list is empty"
},
"FORM": {
"NAME": "Name",
"ADDRESS": "Address",
"NOTES": "Notes"
},
"FORM_ERRORS": {
"NAME_REQUIRED": "Name is required",
"NAME_DUBLICATED": "Name is dublicated",
"ADDRESS_REQUIRED": "Address is required",
"ADDRESS_NOT_VALID": "Address not valid",
"SET_MASTER_PASSWORD": "Set master password",
"ADDRESS_DUBLICATED": "Address is dublicated",
"MAX_LENGTH": "Maximum notes length reached",
"NAME_WRONG": "Contact has wrong name",
"NAME_LENGTH": "The name must be 4-25 characters long"
},
"BUTTON": {
"SEND": "Send",
"EDIT": "Edit",
"DELETE": "Delete",
"ADD": "Add contact",
"ADD_EDIT": "Add/Save",
"GO_TO_WALLET": "Go to wallet",
"IMPORT_EXPORT": "Import/export"
},
"SUCCESS_SENT": "Contact added",
"SUCCESS_SAVE": "Contact is edited",
"SUCCESS_IMPORT": "Contacts is imported",
"ERROR_IMPORT": "Error is occured while reading file!",
"ERROR_TYPE_FILE": "Please import valid .csv file."
},
"ERRORS": {
"NO_MONEY": "Not enough money",
"NOT_ENOUGH_MONEY": "Insufficient funds in account",

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, 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 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<title>icons_5</title>
<g id="f31d9964-f67b-451c-9eb6-78b833647305">
<path class="st0" d="M299.9,197.6c9.3-16.7,14.2-35.5,14.1-54.6c0-70-43.4-105-97-105s-97,35-97,105c-0.1,19.1,4.8,38,14.2,54.6
C89.2,218.8,68.5,274.1,50,346l334-0.1C365.5,274,344.8,218.8,299.9,197.6z M178.1,94c11.6-11.6,27.6-14,38.9-14s27.3,2.4,38.9,14
c13.3,13.2,16.1,34,16.1,49c0,34.7-24.7,63-55,63s-55-28.3-55-63C162,115.7,170.7,101.3,178.1,94z M128.8,256.5
c10.1-14.3,21.1-22.1,36.1-25c31.4,22,73.1,22,104.5-0.1c14.9,2.9,25.9,10.8,35.9,25c8.9,12.6,16.3,29.3,22.7,47.5L106.1,304
C112.5,285.8,119.9,269.1,128.8,256.5z"/>
<path class="st0" d="M32.5,261H0v42h18.2C23,287,27.6,273.3,32.5,261z"/>
<path class="st0" d="M83,182c-0.9-3.6-1.7-7.3-2.4-11H0v42h56.8C64.2,201.6,73,191.2,83,182z"/>
<path class="st0" d="M88.8,81H0v42h79C80.4,108.6,83.7,94.5,88.8,81z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M371,76H269V14H115v62H13v42h35v266h288V118h35V76z M157,56h70v20h-70V56z M294,342H90V118h204V342z"/>
<rect x="136" y="166" class="st0" width="42" height="128"/>
<rect x="206" y="166" class="st0" width="42" height="128"/>
</svg>

After

Width:  |  Height:  |  Size: 651 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path id="details_1_" class="st0" d="M384,112.9L271.1,0L25,246.1v83.8L0.3,354l29.3,30l25.6-25h82.6L384,112.9z M324.6,112.9
l-36.4,36.4l-53.5-53.5l36.4-36.4L324.6,112.9z M70.3,317l-3.3-3.5v-50l138-138l53.5,53.5l-138,138H70.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<polygon class="st0" points="296.5,366 384,288 296.5,210.5 296.5,267 0,267 0,309 296.5,309 "/>
<polygon class="st0" points="87.5,18 0,96 87.5,173.5 87.5,117 384,117 384,75 87.5,75 "/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.4, 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

@ -997,3 +997,29 @@ app-open-wallet-modal {
}
}
}
app-send-modal {
.modal {
@include themify($themes) {
background: themed(modalBackground);
color: themed(mainTextColor);
}
.title {
@include themify($themes) {
border-bottom: 0.2rem solid themed(transparentButtonBorderColor);
}
}
.action-button {
@include themify($themes) {
background-color: themed(blueTextColor);
color: themed(alternativeTextColor);
}
}
}
}

View file

@ -0,0 +1,88 @@
app-contacts, app-add-contacts,
app-contact-send, app-export-import {
flex: 1 1 auto;
padding: 3rem;
min-width: 85rem;
.content {
position: relative;
padding: 3rem;
min-height: 100%;
@include themify($themes) {
background-color: themed(contentBackgroundColor);
color: themed(mainTextColor);
}
.head {
position: absolute;
top: 0;
left: 0;
}
}
}
app-contacts {
table {
.alias {
@include themify($themes) {
color: themed(blueTextColor);
}
}
button {
.icon {
@include themify($themes) {
background-color: themed(blueTextColor);
}
}
span {
@include themify($themes) {
color: themed(mainTextColor)
}
}
}
}
.footer {
@include themify($themes) {
color: themed(blueTextColor);
}
.import-btn {
@include themify($themes) {
color: themed(blueTextColor);
}
.icon {
@include themify($themes) {
background-color: themed(blueTextColor);
}
}
}
}
}
app-contact-send {
.wallets-selection {
button {
@include themify($themes) {
color: themed(blueTextColor);
}
}
}
}

View file

@ -199,6 +199,13 @@ app-history {
}
}
.unlock-transaction {
@include themify($themes) {
background-color: themed(blueTextColor);
}
}
.status.send {
.status-transaction {

View file

@ -9,6 +9,7 @@
@import 'assets/scss/layout/settings';
@import 'assets/scss/layout/sidebar';
@import 'assets/scss/layout/wallet';
@import 'assets/scss/layout/contact';
// MODULES
@import 'assets/scss/modules/head';