Add user feedback for exchange configuration

- Show loading state while connecting to exchange
- Display success/error messages in the form
- Prevent double-submission with disabled button
- Add 30-second timeout with error message
- Register handler for Exchange_connection_result message
- Reload page after successful connection (with delay to show message)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-03-01 17:34:27 -04:00
parent da9d1fc5ee
commit 4e14463465
2 changed files with 103 additions and 16 deletions

View File

@ -3,6 +3,7 @@ class Exchanges {
this.exchanges = {}; this.exchanges = {};
this.balances = {}; this.balances = {};
this.connected_exchanges = []; this.connected_exchanges = [];
this.isSubmitting = false;
} }
initialize() { initialize() {
@ -12,36 +13,100 @@ class Exchanges {
// Extract the text content from each span and store it in the connected_exchanges array // Extract the text content from each span and store it in the connected_exchanges array
this.connected_exchanges = Array.from(spans).map(span => span.textContent.trim()); this.connected_exchanges = Array.from(spans).map(span => span.textContent.trim());
// Register handler for exchange connection results
if (window.UI && window.UI.data && window.UI.data.comms) {
window.UI.data.comms.on('Exchange_connection_result', this.handleConnectionResult.bind(this));
}
} }
status() { status() {
// Reset form state when opening
this.resetFormState();
document.getElementById('exchanges_config_form').style.display = "grid"; document.getElementById('exchanges_config_form').style.display = "grid";
} }
closeForm() { closeForm() {
this.resetFormState();
document.getElementById('exchanges_config_form').style.display = "none"; document.getElementById('exchanges_config_form').style.display = "none";
} }
resetFormState() {
this.isSubmitting = false;
const statusEl = document.getElementById('exchange_status');
const connectBtn = document.getElementById('exchange_connect_btn');
if (statusEl) {
statusEl.style.display = 'none';
document.getElementById('exchange_status_text').textContent = '';
}
if (connectBtn) {
connectBtn.disabled = false;
connectBtn.textContent = 'Connect';
}
}
showStatus(message, type) {
const statusEl = document.getElementById('exchange_status');
const statusText = document.getElementById('exchange_status_text');
if (statusEl && statusText) {
statusText.textContent = message;
statusEl.style.display = 'block';
// Set color based on type
if (type === 'loading') {
statusEl.style.color = '#666';
statusEl.style.backgroundColor = '#f0f0f0';
} else if (type === 'success') {
statusEl.style.color = '#155724';
statusEl.style.backgroundColor = '#d4edda';
} else if (type === 'error') {
statusEl.style.color = '#721c24';
statusEl.style.backgroundColor = '#f8d7da';
}
}
}
validateApiKey(data) { validateApiKey(data) {
return !(data === undefined || data === null || data === ""); return !(data === undefined || data === null || data === "");
} }
postConnection(data) { handleConnectionResult(data) {
this.isSubmitting = false;
const connectBtn = document.getElementById('exchange_connect_btn');
if (connectBtn) {
connectBtn.disabled = false;
connectBtn.textContent = 'Connect';
}
if (data.status === 'success') { if (data.status === 'success') {
// Trigger a page reload this.showStatus('Exchange connected successfully! Reloading...', 'success');
// Reload after a brief delay so user sees the success message
setTimeout(() => {
location.reload(); location.reload();
} }, 1500);
else if (data.status === 'already_connected') { } else if (data.status === 'already_connected') {
alert(data.message); this.showStatus(data.message || 'Exchange is already connected.', 'error');
} } else if (data.status === 'failure' || data.status === 'error') {
else if (data.status === 'failure') { this.showStatus(data.message || 'Failed to connect exchange.', 'error');
alert(data.message); } else {
this.showStatus(data.message || 'Unknown response from server.', 'error');
} }
} }
// Legacy handler for backwards compatibility
postConnection(data) {
this.handleConnectionResult(data);
}
submitApi() { submitApi() {
// Prevent double submission
if (this.isSubmitting) {
return;
}
// Collect the data to submit. // Collect the data to submit.
let exchange = document.getElementById('c_exchanges').value; let exchange = document.getElementById('c_exchanges').value;
let user = window.UI.data.user_name; let user = window.UI.data.user_name;
@ -59,16 +124,33 @@ class Exchanges {
keys = {}; // Clear keys for public exchanges keys = {}; // Clear keys for public exchanges
} else if (!isKeyValid || !isSecretKeyValid) { } else if (!isKeyValid || !isSecretKeyValid) {
// Validate keys for non-public exchanges // Validate keys for non-public exchanges
alert('Enter a valid API key and secret key to register.'); this.showStatus('Please enter a valid API key and secret key.', 'error');
return; // Exit early if validation fails return; // Exit early if validation fails
} }
// Send the data if validation passes or no validation is required. // Show loading state
this.isSubmitting = true;
const connectBtn = document.getElementById('exchange_connect_btn');
if (connectBtn) {
connectBtn.disabled = true;
connectBtn.textContent = 'Connecting...';
}
this.showStatus('Connecting to exchange...', 'loading');
// Send the data
let payload = { 'user': user, 'exch': exchange, 'keys': keys }; let payload = { 'user': user, 'exch': exchange, 'keys': keys };
window.UI.data.comms.sendToApp("config_exchange", payload); window.UI.data.comms.sendToApp("config_exchange", payload);
this.closeForm();
// Refreshes the current page // Set a timeout in case we don't get a response
// setTimeout(function() { location.reload(); }, 200); setTimeout(() => {
if (this.isSubmitting) {
this.isSubmitting = false;
if (connectBtn) {
connectBtn.disabled = false;
connectBtn.textContent = 'Connect';
}
this.showStatus('Connection timed out. Please try again.', 'error');
}
}, 30000); // 30 second timeout
} }
} }

View File

@ -17,10 +17,15 @@
<label for="api_secret_key" style="grid-column: 1; grid-row: 4;">API SECRET KEY:</label> <label for="api_secret_key" style="grid-column: 1; grid-row: 4;">API SECRET KEY:</label>
<input name="api_secret_key" id="api_secret_key" style="grid-column: 2 / span 3; grid-row: 4;"> <input name="api_secret_key" id="api_secret_key" style="grid-column: 2 / span 3; grid-row: 4;">
<!-- Status message area (row 6/8) -->
<div id="exchange_status" style="grid-column: 1 / span 4; grid-row: 6; text-align: center; padding: 10px; display: none;">
<span id="exchange_status_text"></span>
</div>
<!-- buttons (row 8/8)--> <!-- buttons (row 8/8)-->
<div style="grid-column: 1 / span 4; grid-row: 8;"> <div style="grid-column: 1 / span 4; grid-row: 8;">
<button type="button" class="btn cancel" onclick="UI.exchanges.closeForm()">Close</button> <button type="button" class="btn cancel" onclick="UI.exchanges.closeForm()">Close</button>
<button type="button" class="btn next" onclick="UI.exchanges.submitApi()">Connect</button> <button type="button" id="exchange_connect_btn" class="btn next" onclick="UI.exchanges.submitApi()">Connect</button>
</div> </div>
</div><!----End panel 1---------> </div><!----End panel 1--------->