From e8c70fb2a581a41b2499050c18846973c0effe8e Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Thu, 9 Jul 2026 18:43:02 -0300 Subject: [PATCH] fix(apps/amm): surface wallet action failures --- .../qml/components/wallet/AccountControl.qml | 61 ++++++++++++++++--- .../components/wallet/CreateAccountDialog.qml | 29 ++++++++- .../components/wallet/WalletMessageDialog.qml | 58 ++++++++++++++++++ 3 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 apps/amm/qml/components/wallet/WalletMessageDialog.qml diff --git a/apps/amm/qml/components/wallet/AccountControl.qml b/apps/amm/qml/components/wallet/AccountControl.qml index 378291a..1587ad1 100644 --- a/apps/amm/qml/components/wallet/AccountControl.qml +++ b/apps/amm/qml/components/wallet/AccountControl.qml @@ -97,6 +97,20 @@ Item { clipboardProxy.text = "" } + function showWalletMessage(title, message) { + walletMessageDialog.title = title + walletMessageDialog.message = message + walletMessageDialog.open() + } + + function finishAccountCreation(accountId, fallbackError) { + createAccountDialog.busy = false + if (accountId && accountId.length > 0) + createAccountDialog.close() + else + createAccountDialog.createError = fallbackError + } + implicitWidth: root.connected ? connectedButton.width : connectButton.width implicitHeight: 40 @@ -113,8 +127,17 @@ Item { // Re-open an existing wallet; only show the create modal on first run. if (root.backend && root.backend.walletExists) logos.watch(root.backend.openExisting(), - function(ok) { if (!ok) console.warn("openExisting failed") }, - function(error) { console.warn("openExisting error:", error) }) + function(ok) { + if (!ok) + root.showWalletMessage( + qsTr("Unable to connect wallet"), + qsTr("The existing wallet could not be opened. Check the wallet files and try again.")) + }, + function(error) { + root.showWalletMessage( + qsTr("Unable to connect wallet"), + qsTr("Error opening wallet: %1").arg(error)) + }) else createWalletDialog.open() } @@ -490,16 +513,38 @@ Item { CreateAccountDialog { id: createAccountDialog onCreatePublicRequested: { - if (!root.backend) return + if (!root.backend) { + root.finishAccountCreation("", qsTr("Wallet backend is unavailable. Reconnect and try again.")) + return + } + createAccountDialog.createError = "" logos.watch(root.backend.createAccountPublic(), - function(_id) { /* model updates via NOTIFY after refresh */ }, - function(error) { console.warn("createAccountPublic failed:", error) }) + function(id) { + root.finishAccountCreation(id, qsTr("Failed to create account. Please try again.")) + }, + function(error) { + createAccountDialog.busy = false + createAccountDialog.createError = qsTr("Error creating account: %1").arg(error) + }) } onCreatePrivateRequested: { - if (!root.backend) return + if (!root.backend) { + root.finishAccountCreation("", qsTr("Wallet backend is unavailable. Reconnect and try again.")) + return + } + createAccountDialog.createError = "" logos.watch(root.backend.createAccountPrivate(), - function(_id) { /* model updates via NOTIFY after refresh */ }, - function(error) { console.warn("createAccountPrivate failed:", error) }) + function(id) { + root.finishAccountCreation(id, qsTr("Failed to create private account. Please try again.")) + }, + function(error) { + createAccountDialog.busy = false + createAccountDialog.createError = qsTr("Error creating private account: %1").arg(error) + }) } } + + WalletMessageDialog { + id: walletMessageDialog + } } diff --git a/apps/amm/qml/components/wallet/CreateAccountDialog.qml b/apps/amm/qml/components/wallet/CreateAccountDialog.qml index 8070795..368e4b7 100644 --- a/apps/amm/qml/components/wallet/CreateAccountDialog.qml +++ b/apps/amm/qml/components/wallet/CreateAccountDialog.qml @@ -9,13 +9,18 @@ import Logos.Controls Popup { id: root + property string createError: "" + property bool busy: false + signal createPublicRequested() signal createPrivateRequested() modal: true dim: true padding: Theme.spacing.large - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + closePolicy: root.busy + ? Popup.NoAutoClose + : (Popup.CloseOnEscape | Popup.CloseOnPressOutside) // Center on the full-window overlay rather than the small navbar control // this popup is declared inside. @@ -23,6 +28,11 @@ Popup { anchors.centerIn: parent width: 360 + onOpened: { + root.createError = "" + root.busy = false + } + background: Rectangle { color: Theme.palette.backgroundSecondary radius: Theme.spacing.radiusXlarge @@ -74,9 +84,19 @@ Popup { LogosSwitch { id: privateSwitch checked: false + enabled: !root.busy } } + LogosText { + Layout.fillWidth: true + visible: root.createError.length > 0 + text: root.createError + wrapMode: Text.WordWrap + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.error + } + RowLayout { Layout.topMargin: Theme.spacing.medium spacing: Theme.spacing.medium @@ -84,17 +104,20 @@ Popup { LogosButton { text: qsTr("Cancel") Layout.fillWidth: true + enabled: !root.busy onClicked: root.close() } LogosButton { - text: qsTr("Create") + text: root.busy ? qsTr("Creating...") : qsTr("Create") Layout.fillWidth: true + enabled: !root.busy onClicked: { + root.createError = "" + root.busy = true if (privateSwitch.checked) root.createPrivateRequested() else root.createPublicRequested() - root.close() } } } diff --git a/apps/amm/qml/components/wallet/WalletMessageDialog.qml b/apps/amm/qml/components/wallet/WalletMessageDialog.qml new file mode 100644 index 0000000..ea020b5 --- /dev/null +++ b/apps/amm/qml/components/wallet/WalletMessageDialog.qml @@ -0,0 +1,58 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +Popup { + id: root + + property string title: qsTr("Wallet error") + property string message: "" + property string acceptLabel: qsTr("OK") + + modal: true + dim: true + padding: Theme.spacing.large + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + parent: Overlay.overlay + anchors.centerIn: parent + width: 360 + + background: Rectangle { + color: Theme.palette.backgroundSecondary + radius: Theme.spacing.radiusXlarge + border.color: Theme.palette.backgroundElevated + } + + contentItem: ColumnLayout { + width: root.availableWidth + spacing: Theme.spacing.large + + LogosText { + Layout.fillWidth: true + text: root.title + wrapMode: Text.WordWrap + font.pixelSize: Theme.typography.titleText + font.weight: Theme.typography.weightBold + color: Theme.palette.text + } + + LogosText { + Layout.fillWidth: true + text: root.message + wrapMode: Text.WordWrap + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + + LogosButton { + Layout.fillWidth: true + Layout.topMargin: Theme.spacing.medium + text: root.acceptLabel + onClicked: root.close() + } + } +}