From 738b8ec65d7f7abdcc01cc2ea2a23cb4173cf849 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 28 Jun 2026 05:08:41 +0000 Subject: [PATCH] Hi, it's Jules! I've implemented a new UI feature to add a visual loading state to our async forms. I updated the primary buttons on the Login and Registration forms. Now, by safely accessing the triggering element, we temporarily disable the button, lower its opacity, and update its text (e.g., "Logging in...") while the async API request is running. I also included a `finally` block to guarantee the button returns to its original state once the request finishes, ensuring it never gets stuck disabled. This will provide immediate, localized feedback to the user! Co-authored-by: singhaditya21 <53948039+singhaditya21@users.noreply.github.com> --- .Jules/palette.md | 4 ++++ web-demo/js/app.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .Jules/palette.md diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..c403950 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,4 @@ + +## 2026-06-28 - Async Loading States via `e.submitter` +**Learning:** Native form events expose `e.submitter`, enabling direct access to the initiating element (e.g. submit button) for visual loading states, but it must be safely null-checked since programmatic or non-button submissions might leave it undefined. Using `.innerHTML` preserves any icon elements inside the button text. +**Action:** Always capture button state (`innerHTML`) and safely null-check `e.submitter` before modifying properties (`disabled`, `style.opacity`) during async calls, restoring original state in a `finally` block to guarantee recovery. diff --git a/web-demo/js/app.js b/web-demo/js/app.js index 11508de..0aa8b84 100644 --- a/web-demo/js/app.js +++ b/web-demo/js/app.js @@ -144,6 +144,15 @@ class ClimaAI { e.preventDefault(); const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; + const submitBtn = e.submitter; + let originalText = ''; + + if (submitBtn) { + originalText = submitBtn.innerHTML; + submitBtn.disabled = true; + submitBtn.style.opacity = '0.7'; + submitBtn.innerHTML = 'Logging in...'; + } try { this.showToast('Logging in...', 'info'); @@ -155,6 +164,12 @@ class ClimaAI { this.checkSubscription(); } catch (error) { this.showToast(error.message || 'Login failed', 'error'); + } finally { + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.style.opacity = '1'; + submitBtn.innerHTML = originalText; + } } } @@ -163,6 +178,15 @@ class ClimaAI { const name = document.getElementById('registerName').value; const email = document.getElementById('registerEmail').value; const password = document.getElementById('registerPassword').value; + const submitBtn = e.submitter; + let originalText = ''; + + if (submitBtn) { + originalText = submitBtn.innerHTML; + submitBtn.disabled = true; + submitBtn.style.opacity = '0.7'; + submitBtn.innerHTML = 'Creating account...'; + } try { this.showToast('Creating account...', 'info'); @@ -174,6 +198,12 @@ class ClimaAI { this.checkSubscription(); } catch (error) { this.showToast(error.message || 'Registration failed', 'error'); + } finally { + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.style.opacity = '1'; + submitBtn.innerHTML = originalText; + } } }