Finish Send Screen UX Flows & Table Headers for TX + UTXOs (#2099)

* Cypress tests: Fixes, switch to cy.session and new viewport

* feat:(send) finish styles for send screen

* feat:(send) style transaction info screen

* feat:(tables) add task bar for utxo management

* fix rbf overlay

* fixed: element artificat with (un-) checking utxo actions

* feat:(loader) improve loader

* feat:(spectrum) style spectrum success/failure screens

* missing spectrum screen

---------

Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
Co-authored-by: Manolis Mandrapilias <70536101+moneymanolis@users.noreply.github.com>
This commit is contained in:
OTK 2023-02-01 14:45:10 +00:00 committed by GitHub
parent cb04bd5fa4
commit de7ab7f26a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 238 additions and 319 deletions

View file

@ -1098,6 +1098,10 @@ input[type="number"]::-webkit-outer-spin-button,
top: 0px;
}
.top-\[45\%\] {
top: 45%;
}
.z-50 {
z-index: 50;
}
@ -1106,6 +1110,10 @@ input[type="number"]::-webkit-outer-spin-button,
grid-column: span 4 / span 4;
}
.clear-both {
clear: both;
}
.m-1 {
margin: 0.25rem;
}
@ -1557,6 +1565,10 @@ input[type="number"]::-webkit-outer-spin-button,
flex-grow: 0;
}
.border-collapse {
border-collapse: collapse;
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@ -1893,11 +1905,6 @@ input[type="number"]::-webkit-outer-spin-button,
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.bg-dark-300 {
--tw-bg-opacity: 1;
background-color: rgb(111 112 115 / var(--tw-bg-opacity));
}
.bg-dark-400 {
--tw-bg-opacity: 1;
background-color: rgb(87 88 91 / var(--tw-bg-opacity));
@ -2269,6 +2276,10 @@ input[type="number"]::-webkit-outer-spin-button,
display: block;
}
.peer:checked ~ .peer-checked\:flex {
display: flex;
}
.peer:checked ~ .peer-checked\:hidden {
display: none;
}

View file

@ -77,14 +77,6 @@
</ul>
<div class="flex items-center select-none">
<div id="pagination-container" class="flex items-center mx-4 space-x-2">
<a id="pagination-back" class="hidden cursor-pointer w-6"></a>
<span>{{ _("Page") }}</span>
<input id="pagination-idx" class="text-center max-w-[40px] block max-h-[40px] mx-2" type="number" step="1" min="1" value="1"></input>
<div id="pagination-counter" class="block whitespace-nowrap"></div>
<a id="pagination-next" class="hidden cursor-pointer w-6"></a>
</div>
{% include "includes/page-limit-select.html" %}
<div id="export-btn" class="ml-4 hover:bg-dark-700 rounded-lg py-2 px-3">
@ -132,6 +124,15 @@
<tbody class="addr-tbody">
</tbody>
</table>
<div class="flex justify-center w-full mt-3">
<div id="pagination-container" class="flex items-center mx-4 space-x-2">
<a id="pagination-back" class="hidden cursor-pointer w-6"></a>
<span>{{ _("Page") }}</span>
<input id="pagination-idx" class="text-center max-w-[40px] block max-h-[40px] mx-2" type="number" step="1" min="1" value="1"></input>
<div id="pagination-counter" class="block whitespace-nowrap"></div>
<a id="pagination-next" class="hidden cursor-pointer w-6"></a>
</div>
</div>
</template>
<script type="text/javascript">

View file

@ -8,11 +8,20 @@
<!-- <a href="#" id="page_overlay_popup_cancel_button" onclick="cancelOverlay()">{{ _("Cancel") }}</a> -->
<!-- </div> -->
<div class="loader hidden absolute self-center z-100" id="pacman">
<img src="{{ url_for('static', filename='img/loader.gif') }}"/>
<div>{{ _("Loading... It may take a while...") }}</div>
<div class="hidden z-50 absolute w-screen h-screen" id="pacman">
<div class="w-screen overflow-auto h-screen bg-black/40 flex flex-col relative justify-center items-center py-5">
<div class="flex space-x-2 items-center px-4 py-2 box-shadow-xl transition-all absolute max-w-[700px] overflow-hidden rounded-xl bg-dark-800 text-white">
<img class="rounded-full bg-dark-700 w-4 h-4 animate-spin" src="{{ url_for('static', filename='img/refresh.svg') }}"/>
<span>Processing...</span>
</div>
</div>
</div>
<!-- <div class="loader pghidden absolute self-center m-auto top-[45%] z-100" id="pacman"> -->
<!-- <!-1- <img src="{{ url_for('static', filename='img/loader.gif') }}"/> -1-> -->
<!-- <div class="">{{ _("Loading... It may take a while...") }}</div> -->
<!-- </div> -->
<script>
function showPacman(){
document.getElementById('pacman').style.display='flex';

View file

@ -410,22 +410,20 @@
// TODO: get fee diff from specter
newFee += 2;
txDataPopup.innerHTML = `
<form action="${url}" method="POST">
<h1>${rbfType == 'cancel' ? '{{ _("Cancel") }}' : '{{ _("Speed up") }}'} {{ _("the Transaction") }}</h1>
<p>{{ _("You can") }} ${rbfType == 'cancel' ? `{{ _("cancel") }}` : `{{ _("speed up") }}`} {{ _("the transaction by increasing its fee rate") }}${rbfType == 'cancel' ? ` {{ _("and sending the funds back to yourself") }}` : ''}:</p>
<input type="hidden" name="rbf_tx_id" value='${this.tx.txid}' />
<input type="number" min="${newFee}" value="${newFee}" class="fee_rate" name="rbf_fee_rate" id="rbf_fee_rate" min="1" step="any" autocomplete="off"> sat/vbyte
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<br>
<br>
<button type="submit" name="action" value="${rbfType == 'cancel' ? 'rbf_cancel' : 'rbf'}" class="btn centered ${rbfType == 'cancel' ? 'danger' : ''}">${rbfType == 'cancel' ? `{{ _("Cancel transaction") }}` : `{{ _("Speed up!") }}`}</button>
<br>
<span class="toggle_advanced_rbf" data-style="cursor: pointer;">{{ _("Advanced") }} {% if show_advanced_settings %}&#9660;{% else %}&#9654;{% endif %}</span>
<div class="advanced_rbf hidden warning">
<p data-style="max-width: 400px;">{{ _("If you would like further customization, you can click here to fully edit the transaction.") }}<br>{{ _("(advanced, not recommended for new users)") }}</p>
<button type="submit" name="action" value="rbf_edit" class="btn centered">{{ _("Edit the transaction (advanced)") }}</button>
</div>
</form>
<div class="bg-dark-800 p-4 rounded-lg">
<form action="${url}" method="POST">
<h1>${rbfType == 'cancel' ? '{{ _("Cancel") }}' : '{{ _("Speed up transaction") }}'}</h1>
<p>{{ _("You can") }} ${rbfType == 'cancel' ? `{{ _("cancel") }}` : `{{ _("speed up") }}`} {{ _("the transaction by increasing its fee rate") }}${rbfType == 'cancel' ? ` {{ _("and sending the funds back to yourself") }}` : ''}:</p>
<input type="hidden" name="rbf_tx_id" value='${this.tx.txid}' />
<input type="number" min="${newFee}" value="${newFee}" class="fee_rate" name="rbf_fee_rate" id="rbf_fee_rate" min="1" step="any" autocomplete="off" required> sat/vbyte
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button class="button mt-2 bg-accent" type="submit" name="action" value="${rbfType == 'cancel' ? 'rbf_cancel' : 'rbf'}" ${rbfType == 'cancel' ? 'danger' : ''}">${rbfType == 'cancel' ? `{{ _("Cancel transaction") }}` : `{{ _("Speed up!") }}`}</button>
<span class="toggle_advanced_rbf cursor-pointer">{{ _("Advanced") }} {% if show_advanced_settings %}&#9660;{% else %}&#9654;{% endif %}</span>
<div class="advanced_rbf hidden warning flex flex-col">
<button class="button mt-2" type="submit" name="action" value="rbf_edit">{{ _("Fully edit the transaction") }}</button>
</div>
</form>
</div>
`;
txDataPopup.querySelector('.toggle_advanced_rbf').onclick = () => {
let advancedButton = txDataPopup.querySelector('.toggle_advanced_rbf')

View file

@ -69,21 +69,6 @@
text-align: center;
height: 35px;
}
.search-container {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 0.5em;
position: sticky;
top: 0;
background-color: var(--cmap-bg-lighter);
border: 1.5px solid var(--cmap-border);
z-index: 3;
padding: 5px 5px 5px 5px;
}
.page-limit-label {
margin-left: 5px;
}
@ -133,57 +118,6 @@
max-width: 200px;
margin: auto;
}
.selected-rows-action-box {
align-items: center;
}
.selected-rows-action-box .btn {
min-width: 170px;
margin: 5px;
height: 38px;
}
.action-teaser {
max-width: 20em;
text-align: center;
font-size: 15px;
line-height: 12px;
font-weight: 50;
}
.freeze-tx-btn {
border: 1px solid var(--cmap-border);
border-radius: 0.5em;
background-color: var(--cmap-border);
padding: 0.8em;
text-align: center;
font-weight: 250;
}
.freeze-tx-btn:hover {
background-color: var(--cmap-border);
border: 1px solid var(--main-color);
}
.compose-tx-btn {
border: 1px solid var(--cmap-border);
border-radius: 0.5em;
background-color: var(--cmap-border);
padding: 0.8em;
text-align: center;
font-weight: 250;
}
.compose-tx-btn:hover {
background-color: var(--cmap-border);
border: 1px solid var(--main-color);
}
.manage-psbt-btn {
border: 1px solid var(--cmap-border);
border-radius: 0.5em;
background-color: var(--cmap-border);
padding: 0.8em;
text-align: center;
font-weight: 250;
}
.manage-psbt-btn:hover {
background-color: var(--cmap-border);
border: 1px solid var(--main-color);
}
.table-container {
width: 100%;
margin-bottom: 10px;
@ -274,71 +208,61 @@
</li>
</ul>
<div class="flex items-center select-none">
<div id="pagination-container" class="flex items-center mx-4 space-x-2">
<a id="pagination-back" class="hidden cursor-pointer w-6"></a>
<span>{{ _("Page") }}</span>
<input id="pagination-idx" class="text-center max-w-[40px] block max-h-[40px] mx-2" type="number" step="1" min="1" value="1"></input>
<div id="pagination-counter" class="block whitespace-nowrap"></div>
<a id="pagination-next" class="hidden cursor-pointer w-6"></a>
<div class="self-end flex items-center space-x-3 mb-2">
<div class="search-container">
<input class="search w-20 max-h-[40px]" type="text" placeholder="Search..."></input>
<!-- <div id="tooltip-container" class="tooltip-no-grow tooltip-width"> -->
<!-- <tool-tip> -->
<!-- <h4 slot="title">{{ _('Search options') }}</h4> -->
<!-- <span slot="paragraph"> -->
<!-- {{ _("You can search using addresses, tx-IDs, labels, confirmations or amounts.") }}<br><br> -->
<!-- {{ _('Search like this in the time field:') }} <code>%d.%m.%Y %H:%M</code><br><br> -->
<!-- {{ _('Search like this for amounts:') }} <br> -->
<!-- <code> 1</code> {{ _("for anything more than 1") }}<br> -->
<!-- <code>< 1</code> {{ _("for less than 1 BTC (don't forget the space)") }}<br> -->
<!-- </span> -->
<!-- </tool-tip> -->
<!-- </div> -->
</div>
{% include "includes/page-limit-select.html" %}
<div id="export-btn" class="ml-4 hover:bg-dark-700 rounded-lg py-2 px-3">
<div id="export-btn" class="ml-4 hover:bg-dark-700 rounded-lg py-2 px-3 cursor-pointer">
Export
</div>
</div>
<div class="search-container">
<input class="search" type="text" placeholder="Search..."></input>
<!-- <div id="tooltip-container" class="tooltip-no-grow tooltip-width"> -->
<!-- <tool-tip> -->
<!-- <h4 slot="title">{{ _('Search options') }}</h4> -->
<!-- <span slot="paragraph"> -->
<!-- {{ _("You can search using addresses, tx-IDs, labels, confirmations or amounts.") }}<br><br> -->
<!-- {{ _('Search like this in the time field:') }} <code>%d.%m.%Y %H:%M</code><br><br> -->
<!-- {{ _('Search like this for amounts:') }} <br> -->
<!-- <code> 1</code> {{ _("for anything more than 1") }}<br> -->
<!-- <code>< 1</code> {{ _("for less than 1 BTC (don't forget the space)") }}<br> -->
<!-- </span> -->
<!-- </tool-tip> -->
<!-- </div> -->
<!--Right part of the toolbar-->
<div id="action-teaser" class="action-teaser hidden" style="margin-right: 5px; margin-left: 5px;">
<span id="action-teaser-text" class="action-teaser-text">Select checkboxes for actions</span>
</div>
<div class="selected-rows-action-box row hidden">
<form class="freeze-tx-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="button" class="btn freeze-tx-btn">
Un/Freeze UTXOs
<tool-tip width="200px" move-right="true">
<span slot="paragraph">
{{ _("Freezing a UTXO will make Specter disallow spending it.") }}<br><br>
{{ _("This is useful especially for dealing with a dust attack, where you don't want to accidentally spend the dust received.") }}
</span>
</tool-tip>
</button>
</form>
<form class="compose-tx-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="button" class="btn compose-tx-btn">{{ _("Create new transaction") }}</button>
</form>
<a id="manage-psbt-btn" type="button" class="btn manage-psbt-btn hidden" href="">{{ _("Manage unsigned PSBT") }}</a>
</div>
<div id="scrollbar-checkbox-div" class="scrollbar-checkbox-div row hidden">
{{ _("Table scrollbar") }} &nbsp;&nbsp;
<label class="switch">
<input type="checkbox" id="scrollbar-checkbox" class="scrollbar-checkbox" name="scrollbar-checkbox">
<span class="slider"></span>
</label>
</div>
</div>
</nav>
<div>
<div id="action-teaser" class="action-teaser hidden h-12 flex items-center">
<span id="action-teaser-text" class="action-teaser-text">Select checkboxes for actions</span>
</div>
<div class="selected-rows-action-box hidden h-12 flex items-center">
<form class="freeze-tx-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="button" class="btn freeze-tx-btn">
Un/Freeze UTXOs
<!-- <tool-tip width="200px" move-right="true"> -->
<!-- <span slot="paragraph"> -->
<!-- {{ _("Freezing a UTXO will make Specter disallow spending it.") }}<br><br> -->
<!-- {{ _("This is useful especially for dealing with a dust attack, where you don't want to accidentally spend the dust received.") }} -->
<!-- </span> -->
<!-- </tool-tip> -->
</button>
</form>
<form class="compose-tx-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="button" class="btn compose-tx-btn">{{ _("Create new transaction") }}</button>
</form>
<a id="manage-psbt-btn" type="button" class="btn manage-psbt-btn hidden" href="">{{ _("Manage unsigned PSBT") }}</a>
</div>
<div id="scrollbar-checkbox-div" class="scrollbar-checkbox-div row hidden">
{{ _("Table scrollbar") }} &nbsp;&nbsp;
<label class="switch">
<input type="checkbox" id="scrollbar-checkbox" class="scrollbar-checkbox" name="scrollbar-checkbox">
<span class="slider"></span>
</label>
</div>
</div>
<div class="export-container hidden">
<h1>{{ _("Export transactions to CSV") }}</h1>
@ -377,31 +301,40 @@
</th>
</tr>
</thead>
<tr id="feedback-row" class="hidden">
<td id="feedback-row-content">
<div id="rescan-hint" class="hidden">
<p>{{_('Is this not a fresh wallet? Did you expect transactions here? Then click') }} &#128071; {{_('to find them') }}</p>
<a id="go-to-rescan-btn" href="{{ url_for('wallets_endpoint.settings', wallet_alias=wallet_alias, rescan_blockchain=True) }}" class="go-to-rescan-btn btn">Go to blockchain rescan</a>
</div>
</td>
</tr>
<tr id="empty-row" class="hidden">
<td id="empty-row-cell" colspan="9"></td>
</tr>
<tr class="summary-row hidden">
<td class="column-callbackaction"></td>
<td class="column-category" ></td>
<td class="column-txid summary-tx-count"></td>
<td class="column-label optional"></td>
<td class="column-amount summary-amount"></td>
<td class="column-time optional"></td>
<td class="column-confirmations"> </td>
<td class="column-blockhash optional hidden blockhash-summary"></td>
<td class="column-action"></td>
</tr>
<tbody class="tx-tbody">
</tbody>
<tr id="feedback-row" class="hidden">
<td id="feedback-row-content">
<div id="rescan-hint" class="hidden">
<p>{{_('Is this not a fresh wallet? Did you expect transactions here? Then click') }} &#128071; {{_('to find them') }}</p>
<a id="go-to-rescan-btn" href="{{ url_for('wallets_endpoint.settings', wallet_alias=wallet_alias, rescan_blockchain=True) }}" class="go-to-rescan-btn btn">Go to blockchain rescan</a>
</div>
</td>
</tr>
<tr id="empty-row" class="hidden">
<td id="empty-row-cell" colspan="9"></td>
</tr>
<tr class="summary-row hidden">
<td class="column-callbackaction"></td>
<td class="column-category" ></td>
<td class="column-txid summary-tx-count"></td>
<td class="column-label optional"></td>
<td class="column-amount summary-amount"></td>
<td class="column-time optional"></td>
<td class="column-confirmations"> </td>
<td class="column-blockhash optional hidden blockhash-summary"></td>
<td class="column-action"></td>
</tr>
<tbody class="tx-tbody">
</tbody>
</table>
<div class="mt-3">
<div id="pagination-container" class="flex items-center mx-4 space-x-2">
<a id="pagination-back" class="hidden cursor-pointer w-6"></a>
<span>{{ _("Page") }}</span>
<input id="pagination-idx" class="text-center max-w-[40px] block max-h-[40px] mx-2" type="number" step="1" min="1" value="1"></input>
<div id="pagination-counter" class="block whitespace-nowrap"></div>
<a id="pagination-next" class="hidden cursor-pointer w-6"></a>
</div>
</div>
</div>
</template>
@ -1068,13 +1001,6 @@
txRow.rbfCancel.classList.add('hidden');
}
}
// EventListener when hovering over checkboxes
txRow.selectTxImg.addEventListener("mouseover", () => {
this.actionTeaserDiv.style.color = 'orange';
})
txRow.selectTxImg.addEventListener("mouseout", () => {
this.actionTeaserDiv.style.color = 'white';
})
// EventListener when hovering over categories
txRow.category.addEventListener("mouseover", () => {
if (txRow.tx.confirmations == 0) {
@ -1203,6 +1129,7 @@
this.composeTxForm.classList.add('hidden');
this.freezeTxForm.classList.add('hidden');
this.managePSBTBtn.classList.add('hidden');
this.selectedRowsActionBox.classList.add('hidden');
this.actionTeaserDiv.classList.remove("hidden");
}
});

View file

@ -84,6 +84,11 @@
</p>
</div>
<div class="mt-3 cursor-pointer flex" onclick="calculateEstimatedFee()">
<p id="calculated_tx_fee_label">{{ _("Calculate Estimated Fee") }}</p>
<p id="calculated_tx_fee"></p>
</div>
<span class="cursor-pointer mt-3" id="toggle_advanced">{{ _("Advanced") }} {% if show_advanced_settings %}&#9660;{% else %}&#9654;{% endif %}</span>
<div id="advanced_settings" class="{% if not show_advanced_settings %}hidden{% endif %}">
@ -123,12 +128,8 @@
</coin-selection>
</div>
<div class="grid grid-cols-2 gap-3 mt-8">
<button class="button" onclick="calculateEstimatedFee()">
<span id="calculated_tx_fee_label">{{ _("Calculate Estimated Fee") }}</span>
<!-- <span id="calculated_tx_fee"></span> -->
</button>
<button onclick="validateForm(true)" type="button" name="action" value="createpsbt" id="create_psbt_btn" class="button bg-accent text-white">{{ _('Create ')}}<span class="optional">&nbsp;{{ _('unsigned')}}&nbsp;</span>{{ _('transaction') }}</button>
<div class="flex justify-end mt-8">
<button onclick="validateForm(true)" type="button" name="action" value="createpsbt" id="create_psbt_btn" class="button bg-accent text-white">{{ _('Create ')}}<span class="optional">&nbsp;{{ _('Unsigned')}}&nbsp;</span>{{ _('Transaction') }}</button>
</div>
</div>
</form>
@ -595,7 +596,7 @@
}
async function calculateEstimatedFee() {
document.getElementById('calculated_tx_fee_label').innerText = ``;
document.getElementById('calculated_tx_fee').innerText = ``;
if (await validateForm(true) !== true) {
return;
}
@ -609,22 +610,22 @@
if (result.success) {
let psbt = result.psbt;
let fees = parseInt(psbt.fee * 1e8)
document.getElementById('calculated_tx_fee_label').innerText = `: ${fees} sats`;
document.getElementById('calculated_tx_fee').innerText = `: ${fees} sats`;
if('{{ specter.price_check }}' == 'True'){
let altRate = parseFloat('{{ specter.alt_rate }}');
let altSymbol = '{{ specter.alt_symbol }}';
let altAmount = parseFloat((altRate * fees / 1e8).toFixed(2))
if (!isNaN(altAmount) && (altSymbol && altRate)) {
document.getElementById('calculated_tx_fee_label').innerText = `: ${fees} sats (${altAmount}${altSymbol})`;
document.getElementById('calculated_tx_fee').innerText = `: ${fees} sats (${altAmount}${altSymbol})`;
}
}
return fees;
} else {
document.getElementById('calculated_tx_fee_label').innerText = `: ${result.error}`;
document.getElementById('calculated_tx_fee').innerText = `: ${result.error}`;
}
} catch (e) {
console.log(e);
document.getElementById('calculated_tx_fee_label').innerText = `: {{ _("Failed to calculate transaction fees.") }}`;
document.getElementById('calculated_tx_fee').innerText = `: {{ _("Failed to calculate transaction fees.") }}`;
}
return -1;
}

View file

@ -75,21 +75,21 @@
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="tx_info">
{% for i in range(psbt['address']|length) %}
<div data-style="word-break: break-all;">
<div class="flex items-center space-x-2 whitespace-nowrap">
{% if "asset" in psbt %}
{{ _("Sending") }} <b>{{ psbt["amount"][i] | btcamount }}</b> <asset-label data-asset="{{psbt['asset'][i]}}" data-label="{{psbt['asset'][i] | assetlabel}}"></asset-label>
{{ _("Sending ") }} <b>&nbsp;{{ psbt["amount"][i] | btcamount }}&nbsp;</b> <asset-label data-asset="{{psbt['asset'][i]}}" data-label="{{psbt['asset'][i] | assetlabel}}"></asset-label>
{% else %}
{{ _("Sending") }} <b>{{ psbt["amount"][i] | btcunitamount }}</b>
{{ _("Sending ") }} <b>&nbsp;{{ psbt["amount"][i] | btcunitamount }}&nbsp;</b>
{% if specter.unit == 'sat' %}
sats
{% else %}
{% if specter.is_testnet %}t{%endif%}BTC
{% endif %}
{% if specter.price_check %}
<span class="note">({{ psbt["amount"][i] | altunit }})</span>
({{ psbt["amount"][i] | altunit }})
{% endif %}
{% endif %}
{{ _("to") }}<b>
{{ _(" to") }}<b>
{% set addr_label = wallet.getlabel(psbt['address'][i]) %}
<address-label data-address="{{ psbt['address'][i] }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
</b>
@ -105,14 +105,13 @@
{{ _("Acquired") }} <b id="sigscount">{{ psbt['sigs_count'] }}</b> {{ _("of") }} <b>{{ wallet.sigs_required }}</b> {{ _("signatures") }}.
</div>
{% endif %}
<br>
<input type="checkbox" id="showdetails">
<input hidden class="peer" type="checkbox" id="showdetails">
<label for="showdetails" class="btn togglebtn">
{{ _("transaction details") }}
{{ _("Transaction Details") }}
</label>
<div class="hidden" id="tx_details" data-style="word-break: break-all;">
<div class="hidden peer-checked:flex flex-col mt-8" id="tx_details">
<h2 class="tx_details_header">{{ _("Transaction Info") }}</h2>
<p>
<b>{{ _("Total fee:") }}</b> {{ psbt['fee'] | btc2sat }} sats {% if specter.price_check %}<span class="note">&nbsp;({{ psbt['fee'] | altunit }})</span>{% endif %}<br>
@ -127,70 +126,91 @@
<b>{{ _("Inputs count:") }}</b> {{ psbt['tx']['vin'] | length }}<br>
<b>{{ _("Outputs count:") }}</b> {{ psbt['tx']['vout'] | length }}
</p>
<h2 class="tx_details_header">{{ _("Inputs") }} ({{psbt['inputs'] | length}})</h2>
<h2 class="tx_details_header mt-5">{{ _("Inputs") }} ({{psbt['inputs'] | length}})</h2>
{% for input in psbt['inputs'] %}
{% set bg_color = '#131a24' %}
{% set address=input['address'] %}
{% set bg_color = '#925d07' if wallet.is_address_mine(address) else bg_color %}
<p class="tx_info" data-style="text-align: left; background-color: {{ bg_color }};">
<b data-style="margin: auto;">Input #{{loop.index0}}</b><br><br>
<b>{{ _("Transaction id:") }}</b><br>
{{ explorer_link('tx', input['txid'], input['txid'], specter.explorer) }} : {{ input['vout'] }}<br>
{% set addr_label = wallet.getlabel(address) %}
<b>{{ _("Address:") }}</b>
{{ address }}
<br>
<p class="tx_info">
<h3><b>Input #{{loop.index0}}</b></h3>
<div class="flex items-center space-x-2">
<b>{{ _("Transaction id:") }}</b>
<span>
{{ explorer_link('tx', input['txid'], input['txid'], specter.explorer) }} : {{ input['vout'] }}<br>
{% set addr_label = wallet.getlabel(address) %}
</span>
</div>
<div class="flex items-center space-x-2">
<b>{{ _("Address:") }}</b> <span>{{ address }}</span>
</div>
{% if addr_label != address %}
<b>{{ _("Label:") }}</b>
<address-label data-address="{{ address }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
<br>
<div class="flex items-center space-x-2">
<b>{{ _("Label:") }}</b>
<address-label data-address="{{ address }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
</div>
{% endif %}
{% if specter.is_liquid %}
<b>{{ _("Amount:") }}</b> {{ input.get('float_amount') | btcamount }} <asset-label data-asset="{{input['asset']}}" data-label="{{input['asset'] | assetlabel}}"></asset-label>
<div class="flex items-center space-x-2">
<b>{{ _("Amount:") }}&nbsp;</b> {{ input.get('float_amount') | btcamount }} <asset-label data-asset="{{input['asset']}}" data-label="{{input['asset'] | assetlabel}}"></asset-label>
</div>
{% else %}
<b>{{ _("Amount:") }}</b> {{ input.get('float_amount') | btcamount }} {% if input.get('float_amount') != None %} BTC {% endif %} {% if specter.price_check %} <span class="note">&nbsp;({{ input.get('float_amount') | altunit }})</span> {% endif %}
<div class="flex items-center space-x-2">
<b>{{ _("Amount:") }}&nbsp;</b> {{ input.get('float_amount') | btcamount }} {% if input.get('float_amount') != None %} BTC {% endif %} {% if specter.price_check %} <span class="note">&nbsp;({{ input.get('float_amount') | altunit }})</span> {% endif %}
</div>
{% endif %}
</p>
{% endfor %}
<h2 class="tx_details_header">{{ _("Outputs") }} ({{psbt['outputs']|length}})</h2>
<h2 class="tx_details_header mt-5">{{ _("Outputs") }} ({{psbt['outputs']|length}})</h2>
{% for output in psbt['outputs'] %}
{% set address = output['address'] %}
{% set bg_color = '#154984' if output['is_mine'] else '#131a24' %}
<p class="tx_info" data-style="text-align: left; background-color: {{ bg_color }};">
<b>{{ _("Output") }} #{{loop.index0}}</b> {% if output['is_change'] %}(Change){% endif %}<br><br>
{% set addr_label = wallet.getlabel(address) %}
<b>{{ _("Address:") }}</b>
{{ address }}
<br>
<p class="tx_info">
<h3>
<b>{{ _("Output") }} #{{loop.index0}}</b> {% if output['is_change'] %}(Change){% endif %}
{% set addr_label = wallet.getlabel(address) %}
</h3>
<div class="flex items-center space-x-2">
<b>{{ _("Address:") }}&nbsp;</b> {{ address }}
</div>
{% if addr_label != address %}
<b>{{ _("Label:") }}</b>
<address-label data-address="{{ address }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
<br>
<div class="flex items-center space-x-2">
<b>{{ _("Label:") }}</b>
<address-label data-address="{{ address }}" data-label="{{ addr_label }}" data-wallet="{{ wallet_alias }}"></address-label>
</div>
{% endif %}
{% if specter.is_liquid %}
<b>{{ _("Amount:") }}</b> {{ output['float_amount']|btcamount }} <asset-label data-asset="{{output['asset']}}" data-label="{{output['asset'] | assetlabel}}"></asset-label>
<div class="flex items-center space-x-2">
<b>{{ _("Amount:") }}&nbsp;</b> {{ output['float_amount']|btcamount }} <asset-label data-asset="{{output['asset']}}" data-label="{{output['asset'] | assetlabel}}"></asset-label>
</div>
{% else %}
<b>{{ _("Amount:") }}</b> {{ output['float_amount']|btcamount }} BTC {% if specter.price_check %}<span class="note">&nbsp;({{ output['float_amount'] | altunit }})</span>{% endif %}
<div class="flex items-center space-x-2">
<b>{{ _("Amount:") }}&nbsp;</b> {{ output['float_amount']|btcamount }} BTC {% if specter.price_check %}<span class="note">&nbsp;({{ output['float_amount'] | altunit }})</span>{% endif %}
</div>
{% endif %}
</p>
{% endfor %}
{{ _("Raw PSBT:") }}<textarea id="raw-psbt" disabled data-style="background-color: #131a24;">{{ psbt['base64'] }}</textarea>
<div class="row break-row-mobile" data-style="margin:auto">
<a id="download-psbt-btn" class="button"
<h3 class="mt-5">{{ _("Raw PSBT:") }}</h3>
<textarea id="raw-psbt" disabled>{{ psbt['base64'] }}</textarea>
<div class="grid grid-cols-2 gap-3 mt-3">
<a id="download-psbt-btn" class="button m-0"
download="binary_{{ psbt['tx']['hash'] }}.psbt" href="data:application/octet-stream;base64;content-disposition=attachment,{{ psbt['base64'] }}">
<img src="{{ url_for('static', filename='img/file.svg') }}" data-style="width: 26px; margin: 0px;" class="svg-white">
{{ _("Save binary") }}
</a>&nbsp;
<a id="download-psbt-btn" class="button"
</a>
<a id="download-psbt-btn" class="button m-0"
download="base64_{{ psbt['tx']['hash'] }}.psbt" href="data:text/plain;content-disposition=attachment,{{ psbt['base64'] }}">
<img src="{{ url_for('static', filename='img/file.svg') }}" data-style="width: 26px; margin: 0px;" class="svg-white">
{{ _("Save base64") }}
</a>&nbsp;
<a id="copy-raw-psbt-btn" class="button">
</a>
<a id="copy-raw-psbt-btn" class="button m-0">
<img src="{{ url_for('static', filename='img/copy.svg') }}" data-style="width: 26px; margin: 0px;" class="svg-white">
{{ _("Copy Raw PSBT") }}
</a>&nbsp;
<a id="show-qr-psbt-btn" class="button">
</a>
<a id="show-qr-psbt-btn" class="button m-0">
<img src="{{ url_for('static', filename='img/qr-code.svg') }}" data-style="width: 26px; margin: 0px;" class="svg-white">
{{ _("Show QR code") }}
</a>
@ -198,12 +218,12 @@
</div>
</div>
<div id="signing_container" class="signing_container flex-column {% if psbt['raw'] %}hidden{% endif %}" data-style="text-align: center;">
<p data-style="margin-bottom: 15px; margin-top: 0px;">{{ _("Sign transaction with your:") }}</p>
<div id="signing_container" class="signing_container mt-8 flex-column {% if psbt['raw'] %}hidden{% endif %}">
<h3>{{ _("Sign Transaction") }}</h3>
{% for device in wallet.devices %}
{% if device.type != "bitcoincore_watchonly" %}
{% set device_signed = (device.alias in psbt.get("devices_signed")) %}
<button type="button" class="button signing-column-btn" id="{{ device.alias }}_tx_sign_btn" {% if device_signed %} disabled data-style="background-color:#303c49;" {% endif %}>
<button type="button" class="button signing-column-btn bg-accent" id="{{ device.alias }}_tx_sign_btn" {% if device_signed %} disabled {% endif %}>
{{ device.name }} {% if device.alias in psbt.get('devices_signed',[]) %} (&#10004;) {% endif %}
</button>
{% endif %}

View file

@ -1,72 +1,49 @@
{% extends "base.jinja" %}
{% block main %}
<style>
.feedback-icon {
width: 50px;
margin-bottom: 15px
}
.feedback-text {
font-size: 1.1em;
color: #ccc;
}
.feedback-container {
max-width: 100%;
width: 580px;
border: 1px solid var(--cmap-border);
border-radius: 4px;
padding: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button-container {
margin-top: 25px;
}
</style>
<div class="feedback-container">
<div>
<h1>{{ _("Spectrum - Connecting Specter to Electrum") }}</h1><br>
<div style="text-align: center">
<div>
{% if host_type == "list" %}
{% if success %}
<img class="feedback-icon" src="{{ url_for('static', filename='img/party.png') }}"/><br>
<!-- <img class="feedback-icon" src="{{ url_for('static', filename='img/party.png') }}"/><br> -->
{# TODO: Adding the exact name of the server? #}
{% if changed_host %} <span class="feedback-text">{{ _("You switched the Electrum server successfully:") }}</span><br> {% endif %}
<span class="feedback-text">{{ _("Specter is connected via Spectrum to a public Electrum server!") }}</span>
{% if changed_host %} <p>{{ _("You switched the Electrum server successfully:") }}</p> {% endif %}
<p>{{ _("Specter is connected via Spectrum to a public Electrum server!") }}</p>
{% else %}
<img class="feedback-icon" src="{{ url_for('static', filename='img/failed.svg') }}"/><br>
<!-- <img class="feedback-icon" src="{{ url_for('static', filename='img/failed.svg') }}"/><br> -->
{% if changed_host and node_is_running_before_request %}
<span class="feedback-text">{{ _("Cannot connect to the public Electrum server. You changed the settings from a working connection to a server that is not responding.
Consider switching back to the one you had chosen before.") }}</span>
<p>{{ _("Cannot connect to the public Electrum server. You changed the settings from a working connection to a server that is not responding.
Consider switching back to the one you had chosen before.") }}</p>
{% else %}
<span class="feedback-text">{{ _("Cannot connect to the public Electrum server.") }}</span>
<p>{{ _("Cannot connect to the public Electrum server.") }}</p>
{% endif %}
{% endif %}
{% else %}
{% if success %}
<img class="feedback-icon" src="{{ url_for('static', filename='img/party.png') }}"/><br>
{% if changed_host %} <span class="feedback-text">{{ _("You switched the Electrum server successfully:") }}</span><br> {% endif %}
<span class="feedback-text">{{ _("Specter is connected via Spectrum to a manually configured Electrum server!") }}</span>
<!-- <img class="feedback-icon" src="{{ url_for('static', filename='img/party.png') }}"/><br> -->
{% if changed_host %} <p>{{ _("You switched the Electrum server successfully:") }}</p> {% endif %}
<p>{{ _("Specter is connected via Spectrum to a manually configured Electrum server!") }}</p>
{% else %}
<img class="feedback-icon" src="{{ url_for('static', filename='img/failed.svg') }}"/><br>
<!-- <img class="feedback-icon" src="{{ url_for('static', filename='img/failed.svg') }}"/><br> -->
{% if changed_host and node_is_running_before_request %}
<span class="feedback-text">{{ _("Cannot connect to the manually configured Electrum server. You changed the settings from a working connection to a server that is not responding.
Consider switching back to the one you had chosen before.") }}</span>
<p>{{ _("Cannot connect to the manually configured Electrum server. You changed the settings from a working connection to a server that is not responding.
Consider switching back to the one you had chosen before.") }}</p>
{% elif not changed_host and check_port_and_ssl %}
<span class="feedback-text">{{ _("Cannot connect to the manually configured Electrum server. Double-check that the port and SSL settings are correct.") }}</span>
<p>{{ _("Cannot connect to the manually configured Electrum server. Double-check that the port and SSL settings are correct.") }}</p>
{% elif changed_host and check_port_and_ssl %}
<span class="feedback-text">{{ _("Cannot connect to the manually configured Electrum server. Double-check all the configuration settings.") }}</span>
<p>{{ _("Cannot connect to the manually configured Electrum server. Double-check all the configuration settings.") }}</p>
{% else %}
<span class="feedback-text">{{ _("Cannot connect to the manually configured Electrum server.") }}</span>
<p>{{ _("Cannot connect to the manually configured Electrum server.") }}</p>
{% endif %}
{% endif %}
{% endif %}
</div>
<div class="button-container">
{% if success %}
<a class="btn" href="{{ url_for('welcome_endpoint.index') }}" >{{ _("Let's go!") }}</a>
<a class="button bg-accent mt-8" href="{{ url_for('welcome_endpoint.index') }}" >{{ _("Let's go!") }}</a>
{% else %}
<a class="btn" id="back-button">{{ _("Back to Spectrum settings") }}</a>
<a class="button mt-8" id="back-button">{{ _("Back to Spectrum settings") }}</a>
{% endif %}
</div>
</div>

View file

@ -1,42 +1,17 @@
{% extends "base.jinja" %}
{% block main %}
<style>
.feedback-icon {
width: 60px;
margin-bottom: 15px
}
.feedback-text {
font-size: 1.1em;
color: #ccc;
}
.feedback-container {
max-width: 100%;
width: 580px;
border: 1px solid var(--cmap-border);
border-radius: 4px;
padding: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button-container {
margin-top: 25px;
}
</style>
<div class="feedback-container">
<div>
<h1>{{ _("Spectrum - Connecting Specter to Electrum") }}</h1><br>
<div style="text-align: center">
<div>
{% if core_node_exists %}
<img class="feedback-icon" src="{{ url_for('static', filename='img/not-supported.svg') }}"/><br>
<span class="feedback-text">{{ _("In the alpha version of Spectrum, it's not supported to use a Bitcoin Core node alongside the Spectrum node. Your Spectrum node was not saved! Delete the Bitcoin Core connection and try again.") }}</span>
<p>{{ _("With the beta version of Spectrum, it's not supported to use a Bitcoin Core connection alongside a Spectrum connection. Your connection was not saved! Delete the Bitcoin Core connection and try again.") }}</p>
{% endif %}
</div>
<div class="button-container">
<div>
{% if core_node_exists %}
<a class="btn" id="back-button">{{ _("Back to Spectrum settings") }}</a>
<a class="button mt-8" id="back-button">{{ _("Back to Spectrum settings") }}</a>
{% else %}
<a class="btn" href="{{ url_for('welcome_endpoint.index') }}" >{{ _("Let's go!") }}</a>
<a class="button bg-accent mt-8" href="{{ url_for('welcome_endpoint.index') }}" >{{ _("Let's go!") }}</a>
{% endif %}
</div>
</div>