commit dd31eb76fe236f4b5eceb6f17e648b4abb0226eb
parent 76f148eb28456d356780b798d66821892892b6a0
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Thu, 13 Aug 2026 09:29:02 +0200
Move address book form into recipient picker
Diffstat:
3 files changed, 37 insertions(+), 34 deletions(-)
diff --git a/src/adapters/http/index_html.rs b/src/adapters/http/index_html.rs
@@ -264,8 +264,8 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html>
.address-book-name { font-weight: 800; color: #eef6f8; overflow-wrap: anywhere; }
.address-book-actions { display: flex; gap: 6px; align-items: center; }
.address-book-modal { width: min(560px, 100%); }
- .address-book-modal form { width: 100%; }
- .address-book-modal form label { flex: 1 0 100%; }
+ .address-book-modal form { width: 100%; display: grid; gap: 10px; }
+ .address-book-form { display: grid; gap: 10px; margin-top: 14px; padding-top: 14px; border-top: 1px solid #2f363c; }
.address-book-modal-actions { flex: 1 0 100%; width: 100%; display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
.address-book-picker-list { display: grid; gap: 8px; }
.address-book-picker-row { width: 100%; display: grid; gap: 3px; text-align: left; border: 1px solid #2f363c; border-radius: 8px; padding: 10px; background: #111316; color: inherit; }
@@ -1378,17 +1378,32 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html>
</div>
</section>
</div>
- <div class="setup-overlay transaction-overlay" x-show="addressBookModalOpen" x-transition.opacity @click.self="closeAddressBookModal()" role="dialog" aria-modal="true" aria-label="Address Book">
+ <div class="setup-overlay transaction-overlay" x-show="addressBookPickerOpen" x-transition.opacity @click.self="closeAddressBookPicker()" role="dialog" aria-modal="true" aria-labelledby="address-book-picker-title">
<section class="tx-modal address-book-modal">
<div class="tx-modal-head">
<div class="tx-modal-title">
- <span class="pill">Address Book</span>
+ <span class="pill">Send</span>
+ <h2 id="address-book-picker-title">Choose Contact</h2>
+ </div>
+ <div class="address-book-actions">
+ <button class="icon-button" type="button" x-show="!addressBookModalOpen" @click="openAddressBookModal()" title="Add contact" aria-label="Add contact">
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14"></path><path d="M5 12h14"></path></svg>
+ </button>
+ <button class="icon-button" type="button" @click="closeAddressBookPicker()" title="Close" aria-label="Close">
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
+ </button>
</div>
- <button class="icon-button" type="button" @click="closeAddressBookModal()" title="Close" aria-label="Close">
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
- </button>
</div>
- <form @submit.prevent="saveAddressBookEntry">
+ <div class="address-book-picker-list" x-show="!addressBookModalOpen">
+ <template x-for="entry in addressBookEntries()" :key="entry.address">
+ <button class="address-book-picker-row" type="button" @click="selectTransferContact(entry.address)" :title="`Send to ${entry.name}`">
+ <span class="address-book-name" x-text="entry.name"></span>
+ <code class="tx-value hash" x-text="short(entry.address)"></code>
+ </button>
+ </template>
+ <div class="tx-modal-empty" x-show="addressBookEntries().length === 0">No saved addresses</div>
+ </div>
+ <form class="address-book-form" x-show="addressBookModalOpen" @submit.prevent="saveAddressBookEntry">
<label>Name<input x-model="addressBookDraftName" autocomplete="off" required></label>
<label>Address<input x-model="addressBookDraftAddress" autocomplete="off" required :class="{ invalid: addressBookDraftAddress && !validAddressBookAddress(addressBookDraftAddress) }"></label>
<div class="setup-feedback error" x-show="addressBookDraftAddress && !validAddressBookAddress(addressBookDraftAddress)">Address must be a 64 character hex public key</div>
@@ -1396,6 +1411,9 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html>
<button class="icon-button modal-delete-button" type="button" x-show="addressBookEditingAddress" @click="removeAddressBookEntry({ address: addressBookEditingAddress, name: addressBookDraftName || addressBookEditingAddress })" title="Delete contact" aria-label="Delete contact">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16"></path><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M6 7l1 14h10l1-14"></path><path d="M9 7V4h6v3"></path></svg>
</button>
+ <button class="icon-button" type="button" @click="closeAddressBookModal()" title="Back to contacts" aria-label="Back to contacts">
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6"></path></svg>
+ </button>
<button class="primary icon-button" type="submit" title="Save contact" aria-label="Save contact">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z"></path><path d="M7 3v6h8"></path><path d="M7 21v-8h10v8"></path></svg>
</button>
@@ -1403,27 +1421,6 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html>
</form>
</section>
</div>
- <div class="setup-overlay transaction-overlay" x-show="addressBookPickerOpen" x-transition.opacity @click.self="closeAddressBookPicker()" role="dialog" aria-modal="true" aria-labelledby="address-book-picker-title">
- <section class="tx-modal address-book-modal">
- <div class="tx-modal-head">
- <div class="tx-modal-title">
- <span class="pill">Send</span>
- <h2 id="address-book-picker-title">Choose Contact</h2>
- </div>
- <button class="icon-button" type="button" @click="closeAddressBookPicker()" title="Close" aria-label="Close">
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
- </button>
- </div>
- <div class="address-book-picker-list">
- <template x-for="entry in addressBookEntries()" :key="entry.address">
- <button class="address-book-picker-row" type="button" @click="selectTransferContact(entry.address)" :title="`Send to ${entry.name}`">
- <span class="address-book-name" x-text="entry.name"></span>
- <code class="tx-value hash" x-text="short(entry.address)"></code>
- </button>
- </template>
- </div>
- </section>
- </div>
<div class="setup-overlay" x-show="showingSetup()" x-transition.opacity role="dialog" aria-modal="true" aria-labelledby="setup-title">
<section class="setup-modal">
<div class="setup-modal-head">
diff --git a/src/adapters/http/tests.rs b/src/adapters/http/tests.rs
@@ -1626,6 +1626,13 @@ fn wallet_screen_includes_address_book_alias_controls() {
assert!(super::INDEX_HTML.contains("selectTransferContact(entry.address)"));
assert!(super::INDEX_HTML.contains("editAddressBookEntry(entry)"));
assert!(
+ super::INDEX_HTML
+ .contains("x-show=\"!addressBookModalOpen\" @click=\"openAddressBookModal()\"")
+ );
+ assert!(
+ super::INDEX_HTML.contains("class=\"address-book-form\" x-show=\"addressBookModalOpen\"")
+ );
+ assert!(
super::INDEX_HTML.contains("removeAddressBookEntry({ address: addressBookEditingAddress")
);
assert!(super::INDEX_HTML.contains("aria-label=\"Choose contact\""));
@@ -1641,6 +1648,8 @@ fn wallet_screen_includes_address_book_alias_controls() {
assert!(app_js.contains("Address is already saved"));
assert!(app_js.contains("validAddressBookAddress(address)"));
assert!(app_js.contains("openAddressBookPicker()"));
+ assert!(app_js.contains("this.addressBookPickerOpen = true;"));
+ assert!(!app_js.contains("No contacts saved yet"));
assert!(app_js.contains("await this.submitForm(\"/api/address-book\""));
assert!(app_js.contains("\"/api/address-book\""));
assert!(app_js.contains("addressLabel(address)"));
diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js
@@ -2223,6 +2223,7 @@ window.iunaApp = function iunaApp() {
this.addressBookEditingAddress = entry?.address || null;
this.addressBookDraftAddress = entry?.address || "";
this.addressBookDraftName = entry?.name || "";
+ this.addressBookPickerOpen = true;
this.addressBookModalOpen = true;
},
@@ -2234,15 +2235,12 @@ window.iunaApp = function iunaApp() {
},
openAddressBookPicker() {
- if (this.addressBookEntries().length === 0) {
- this.showFlash("No contacts saved yet", "error");
- return;
- }
this.addressBookPickerOpen = true;
},
closeAddressBookPicker() {
this.addressBookPickerOpen = false;
+ this.closeAddressBookModal();
},
async saveAddressBookEntry() {
@@ -2290,7 +2288,6 @@ window.iunaApp = function iunaApp() {
this.addressBook = nextBook;
this.config = { ...this.config, address_book: nextBook };
if (this.addressBookEditingAddress === entry.address) this.closeAddressBookModal();
- if (this.addressBookEntries().length === 0) this.closeAddressBookPicker();
this.showFlash(`Removed ${entry.name}`, "success");
} catch (error) {
this.showFlash(error.message, "error");