Skip to content

Commit

Permalink
Contacts Implementation (#184)
Browse files Browse the repository at this point in the history
* Initial 'MPW Contacts' implementation

* Add new Receive dialog + input validator

* Add URI-based Contact Requests

* Add Send QR Contacts Integration

* Add JSDoc param

* Add Unencrypted wallet safeguards

* Further safeguards

* Improve Scanner decoder

* Final (hopefully) decoding correction

* Add dedicated Contact Scanner

* Add re-rendering + safeguards

* Prettier

* Add ability to edit Contact Name

* Add Avatars + Avatar editing

* [HTML+JS] confirmModal purple theme support

* [HTML+CSS+JS] New contacts design

* Prettier

* Fix Contact selector + add Back button

* Add Delete confirmation + shorten XPubs

* Fix Confirm Popup weirdness

* Add partial Activity integration

* Prevent adding yourself as a Contact

* Safeguard Contact selection from unsaved wallets

* Check validity of Payment Prompts

* Drop max name to 32 char, improve Edit safety

* Fix Avatar selection + further HTML sanitisation

* Add secure URI construction, encoding and copying

* Encode and Decode URI names in HEX

* Wrap long names

* Add further Name safety-rails

* Check manual Contact entries for duplicates

* Standardise XPub validation

* Move P2PKH address checks to Standardised checker

* Full i18n system integration

* Remove duplicate i18n key
No idea why `lint` didn't warn me of this before pushing!

* Fix Receive Toggle i18n rendering

---------

Co-authored-by: BreadJS <83626012+BreadJS@users.noreply.github.com>
  • Loading branch information
JSKitty and BreadJS authored Aug 26, 2023
1 parent 7fdab99 commit e6cc365
Show file tree
Hide file tree
Showing 19 changed files with 1,914 additions and 55 deletions.
83 changes: 83 additions & 0 deletions assets/style/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,10 @@ a {
color:#fff;
}

.exportKeysModalColor h3 {
color:#d5adff!important;
}

.dcWallet-privateKeyDiv {
display: flex;
width: 100%;
Expand Down Expand Up @@ -3434,4 +3438,83 @@ select.form-control option {
.sliderDisplay {
display: none;
}
}

@media (min-width: 768px) {
.max-w-450 {
max-width: 450px;
}
}

.contactsList .contactItem {
transition: all .25s ease-in-out;
}

.contactsList .contactItem:hover {
background-color:#0000001c!important;
}

.contactsList .contactItem:nth-child(odd) {
background-color:#00000017
}

.contactsList .contactItem:nth-child(even) {
background-color:#00000033
}

.contactsList .addContact .contactName input,
.contactsList .addContact .contactAddr input,
.contactsList .addContactBtn,
.contactsList .qrContactBtn {
color: #ffffff;
background-color: #f2f2f233;
border: 1px solid #f2f2f224;
}

.contactsList .addContact .contactName input::placeholder,
.contactsList .addContact .contactAddr input::placeholder {
color: #dddddd;
}

.contactsList .addContact .contactName input {
border-top-right-radius:0px;
border-bottom-right-radius:0px;
}

.contactsList .addContact .contactAddr input {
border-left:none;
border-right:none;
border-radius:0px;
}

.contactsList .addContactBtn {
cursor: pointer;
border-radius: 0px;
padding: 0px 10px;
font-weight: bold;
width: fit-content;
height: 43px;
display: flex;
align-items: center;
transition: all .125s ease-in-out;
border-right:none;
}

.contactsList .qrContactBtn {
cursor: pointer;
border-radius: 7px;
padding: 0px 10px;
font-weight: bold;
width: fit-content;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
height: 43px;
display: flex;
align-items: center;
transition: all .125s ease-in-out;
}

.contactsList .addContactBtn:hover,
.contactsList .qrContactBtn:hover {
background-color: #f2f2f24d;
}
44 changes: 36 additions & 8 deletions index.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,23 @@
<!-- // NAVBAR -->

<!-- // QR MODAL -->
<div class="modal fade" id="qrModal" tabindex="-1" role="dialog" aria-labelledby="qrModalLabel" aria-hidden="true">
<div class="modal fade" style="width: 375px;" id="qrModal" tabindex="-1" role="dialog" aria-labelledby="qrModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content exportKeysModalColor">
<div class="modal-header">
<h5 class="modal-title" id="qrModalLabel"><span data-i18n="address" >Address</span> QR</h5>
<h5 class="modal-title" data-i18n="receive" id="qrModalLabel">Receive</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<i class="fa-solid fa-xmark closeCross"></i>
</button>
</div>
<div class="modal-body center-text">
<code id="ModalQRLabel" class="wallet-code"></code>
<div id="ModalQR" style="padding: 17px 10px 10px 10px;" class="auto-fit"></div>
<center>
<div id="ModalQRReceiveTypeBtn" onclick="MPW.guiToggleReceiveType()" style="cursor: pointer; border: 0px; border-radius: 7px; padding: 6px 10px; margin: 0px 1px; background: linear-gradient(183deg, #9621ff9c, #7d21ffc7); color: #fff; font-weight: bold; width: fit-content; margin: 20px 0px;">
Change to Address
</div>
</center>
</div>
</div>
</div>
Expand Down Expand Up @@ -130,7 +135,7 @@ <h5 data-i18n="balanceBreakdown" class="modal-title" id="walletBreakdownModalLab

<br />
<!-- // MNEMONIC MODAL -->
<div class="modal fade" id="mnemonicModal" tabindex="-1" role="dialog" aria-labelledby="qrModalLabel" aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal fade" id="mnemonicModal" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body center-text">
Expand Down Expand Up @@ -166,8 +171,8 @@ <h5 data-i18n="balanceBreakdown" class="modal-title" id="walletBreakdownModalLab
<br />
<!-- // CONFIRM MODAL -->
<div class="modal" id="confirmModal" tabindex="-1" style="z-index: 2000; background-color: #00000063;" role="dialog" aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered max-w-600" role="document">
<div class="modal-content">
<div id="confirmModalDialog" class="modal-dialog modal-dialog-centered max-w-600" role="document">
<div id="confirmModalMain" class="modal-content">
<div class="modal-header" id="confirmModalHeader">
<h3 class="modal-title" id="confirmModalTitle" style="text-align: center; width: 100%; color: #8e21ff;"></h3>
</div>
Expand Down Expand Up @@ -411,13 +416,32 @@ <h3 class="modal-title" id="redeemCodeModalTitle" style="text-align: center; wid
</div>
<div class="modal-footer" hidden="true" id="redeemCodeModalButtons">
<button type="button" onclick="MPW.promoConfirm()" id="redeemCodeModalConfirmButton" class="pivx-button-big" style="float: right;">Redeem</button>
<button type="button" data-dismiss="modal" aria-label="Close" class="pivx-button-big" style="float: right; opacity: 0.7;">Close</button>
<button type="button" data-dismiss="modal" aria-label="Close" data-i18n="popupClose" class="pivx-button-big" style="float: right; opacity: 0.7;">Close</button>
</div>
</div>
</div>
</div>
<!-- // Redeem Code (PIVX Promos) -->

<!-- Contacts Modal -->
<div class="modal" id="contactsModal" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered max-w-450" role="document">
<div class="modal-content exportKeysModalColor">
<div class="modal-header" id="contactsModalHeader">
<h3 class="modal-title" id="contactsModalTitle" data-i18n="contacts" style="text-align: center; width: 100%; color: #d5adff;">Contacts</h3>
</div>
<div class="modal-body px-0">
<div id="contactsList" class="contactsList">
</div>
</div>
<div class="modal-footer" hidden="true">
<button type="button" data-dismiss="modal" aria-label="Close" class="pivx-button-big" data-i18n="popupClose" style="color:#fff; float: right; opacity: 0.8;">Close</button>
</div>
</div>
</div>
</div>
<!-- // Contacts Modal -->

<!-- WALLET FEATURES -->
<div id="guiWallet" style="display: none;">
<div class="row p-0">
Expand Down Expand Up @@ -445,6 +469,9 @@ <h3 class="noselect balance-title">
<a class="dropdown-item ptr" onclick="MPW.openExplorer()">
<i class="fa-solid fa-magnifying-glass"></i> <span data-i18n="viewOnExplorer">View on Explorer</span>
</a>
<a class="dropdown-item ptr" onclick="MPW.guiRenderContacts()" data-toggle="modal" data-target="#contactsModal">
<i class="fa-solid fa-address-book"></i> <span data-i18n="contactsBook">Contacts</span>
</a>
<a id="guiExportWalletItem" class="dropdown-item ptr" data-toggle="modal" data-target="#exportPrivateKeysModal" data-backdrop="static" data-keyboard="false" onclick="MPW.toggleExportUI()">
<i class="fas fa-key"></i> <span data-i18n="export">Export</span>
</a>
Expand Down Expand Up @@ -481,7 +508,7 @@ <h3 class="noselect balance-title">
</div>

<div class="col-6 d-flex" style="justify-content: flex-end;">
<div class="dcWallet-sDeposit" data-toggle="modal" data-target="#qrModal">
<div class="dcWallet-sDeposit" onclick="MPW.guiRenderCurrentReceiveModal()" data-toggle="modal" data-target="#qrModal">
<i class="fas fa-arrow-down"></i>
</div>
</div>
Expand Down Expand Up @@ -547,9 +574,10 @@ <h3 data-i18n="viewPrivateKey">View Private Key?</h3>
<label data-i18n="address">Address</label><br />

<div class="input-group mb-3">
<input class="btn-group-input" data-i18n="receivingAddress" style="font-family: monospace;" type="text" id="address1s" placeholder="Receiving address" autocomplete="nope" />
<input class="btn-group-input" data-i18n="receivingAddress" oninput='MPW.guiCheckRecipientInput(event)' style="font-family: monospace;" type="text" id="address1s" placeholder="Receiving address" autocomplete="nope" />
<div class="input-group-append">
<span class="input-group-text ptr" onclick="MPW.openSendQRScanner()"><i class="fa-solid fa-qrcode fa-2xl"></i></span>
<span class="input-group-text ptr" onclick="MPW.guiSelectContact(MPW.doms.domAddress1s)"><i class="fa-solid fa-address-book fa-2xl"></i></span>
</div>
</div>

Expand Down
44 changes: 44 additions & 0 deletions locale/de/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,37 @@ export const de_translation = {
paymentRequestMessage: 'Beschreibung (vom Händler)', //Description (from the merchant)
send: 'Senden', //Send

// Contacts System
receive: '', //Receive
contacts: '', //Contacts
name: '', //Name
username: '', //Username
addressOrXPub: '', //Address or XPub
back: '', //Back
chooseAContact: '', //Choose a Contact
createContact: '', //Create Contact
encryptFirstForContacts: '', //Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier!
shareContactURL: '', //Share Contact URL
setupYourContact: '', //Setup your Contact
receiveWithContact: '', //Receive using a simple username-based Contact
onlyShareContactPrivately: '', //<b>Only</b> share your Contact with trusted people (family, friends)

/* Context: The "Change to" is used in-app with one of the Three options below it, i.e: "Change to Contact" */
changeTo: '', //Change to
contact: '', //Contact
xpub: '', //XPub

addContactTitle: '', //Add {strName} to Contacts
addContactSubtext: '', //Once added you\'ll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice \'n easy.
addContactWarning: '', //Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources!

editContactTitle: '', //Change "{strName}" Contact
newName: '', //New Name

removeContactTitle: '', //Remove {strName}?
removeContactSubtext: '', //Are you sure you wish to remove {strName} from your Contacts?
removeContactNote: '', //You can add them again any time in the future.

// Export
privateKey: 'Privater Schlüssel', //Private Key
viewPrivateKey: 'Zeige privaten Schlüssel', //View Private Key?
Expand Down Expand Up @@ -318,6 +349,19 @@ export const de_translation = {
MN_COLLAT_NOT_SUITABLE: 'Dies ist keine gültige UTXO für eine Masternode', //This is not a suitable UTXO for a Masternode
MN_CANT_CONNECT: 'Keine Verbindung zum RPC-Knoten möglich!', //Unable to connect to RPC node!

/* Contacts System Alerts */
CONTACTS_ENCRYPT_FIRST: '', //You need to hit "{button}" before you can use Contacts!
CONTACTS_NAME_REQUIRED: '', //A name is required!
CONTACTS_NAME_TOO_LONG: '', //That name is too long!
CONTACTS_CANNOT_ADD_YOURSELF: '', //You cannot add yourself as a Contact!
CONTACTS_ALREADY_EXISTS: '', //<b>Contact already exists!</b><br>You already saved this contact
CONTACTS_NAME_ALREADY_EXISTS: '', //<b>Contact name already exists!</b><br>This could potentially be a phishing attempt, beware!
CONTACTS_EDIT_NAME_ALREADY_EXISTS: '', //<b>Contact already exists!</b><br>A contact is already called "{strNewName}"!
CONTACTS_KEY_ALREADY_EXISTS: '', //<b>Contact already exists, but under a different name!</b><br>You have {newName} saved as <b>{oldName}</b> in your contacts
CONTACTS_NOT_A_CONTACT_QR: '', //This isn\'t a Contact QR!
CONTACTS_ADDED: '', //<b>New Contact added!</b><br>{strName} has been added, hurray!
CONTACTS_YOU_HAVE_NONE: '', //You have no contacts!

PROPOSAL_FINALISED: 'Antrag finalisiert!', //Proposal finalized!
PROPOSAL_UNCONFIRMED: 'Der Antrag wurde noch nicht bestätigt.', //The proposal hasn\'t been confirmed yet.
PROPOSAL_EXPIRED: 'Der Antrag ist ausgelaufen. Erstelle einen neuen.', //The proposal has expired. Create a new one.
Expand Down
55 changes: 55 additions & 0 deletions locale/en/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,42 @@ export const en_translation = {
paymentRequestMessage: 'Description (from the merchant)', //
send: 'Send', //

// Contacts System
receive: 'Receive', //
contacts: 'Contacts', //
name: 'Name', //
username: 'Username', //
addressOrXPub: 'Address or XPub', //
back: 'Back', //
chooseAContact: 'Choose a Contact', //
createContact: 'Create Contact', //
encryptFirstForContacts:
'Once you hit "{button}" in the Dashboard, you can create a Contact to make receiving PIV easier!', //
shareContactURL: 'Share Contact URL', //
setupYourContact: 'Setup your Contact', //
receiveWithContact: 'Receive using a simple username-based Contact', //
onlyShareContactPrivately:
'<b>Only</b> share your Contact with trusted people (family, friends)', //

/* Context: The "Change to" is used in-app with one of the Three options below it, i.e: "Change to Contact" */
changeTo: 'Change to', //
contact: 'Contact', //
xpub: 'XPub', //

addContactTitle: 'Add {strName} to Contacts', //
addContactSubtext:
"Once added you'll be able to send transactions to {strName} by their name (either typing, or clicking), no more addresses, nice 'n easy.", //
addContactWarning:
'Ensure that this is the real "{strName}", do not accept Contact requests from unknown sources!', //

editContactTitle: 'Change "{strName}" Contact', //
newName: 'New Name', //

removeContactTitle: 'Remove {strName}?', //
removeContactSubtext:
'Are you sure you wish to remove {strName} from your Contacts?', //
removeContactNote: 'You can add them again any time in the future.', //

// Export
privateKey: 'Private Key', //
viewPrivateKey: 'View Private Key?', //
Expand Down Expand Up @@ -308,6 +344,25 @@ export const en_translation = {
MN_COLLAT_NOT_SUITABLE: 'This is not a suitable UTXO for a Masternode',
MN_CANT_CONNECT: 'Unable to connect to RPC node!',

/* Contacts System Alerts */
CONTACTS_ENCRYPT_FIRST:
'You need to hit "{button}" before you can use Contacts!',
CONTACTS_NAME_REQUIRED: 'A name is required!',
CONTACTS_NAME_TOO_LONG: 'That name is too long!',
CONTACTS_CANNOT_ADD_YOURSELF: 'You cannot add yourself as a Contact!',
CONTACTS_ALREADY_EXISTS:
'<b>Contact already exists!</b><br>You already saved this contact',
CONTACTS_NAME_ALREADY_EXISTS:
'<b>Contact name already exists!</b><br>This could potentially be a phishing attempt, beware!',
CONTACTS_EDIT_NAME_ALREADY_EXISTS:
'<b>Contact already exists!</b><br>A contact is already called "{strNewName}"!',
CONTACTS_KEY_ALREADY_EXISTS:
'<b>Contact already exists, but under a different name!</b><br>You have {newName} saved as <b>{oldName}</b> in your contacts',
CONTACTS_NOT_A_CONTACT_QR: "This isn't a Contact QR!",
CONTACTS_ADDED:
'<b>New Contact added!</b><br>{strName} has been added, hurray!',
CONTACTS_YOU_HAVE_NONE: 'You have no contacts!',

PROPOSAL_FINALISED: 'Proposal Launched!',
PROPOSAL_UNCONFIRMED: "The proposal hasn't confirmed yet",
PROPOSAL_EXPIRED: 'The proposal has expired. Create a new one.',
Expand Down
Loading

0 comments on commit e6cc365

Please sign in to comment.