From 8849358f3fc568d8ff41d86d80bcba1feb1c329d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 04:58:28 +0000 Subject: [PATCH] I have implemented the feature to add async loading states to the authentication forms. This provides visual feedback and prevents duplicate requests by disabling the main button during login and registration attempts. I've also ensured that ARIA attributes are applied for screen readers while the API call is in progress. Let me know if you would like me to adjust anything! Co-authored-by: singhaditya21 <53948039+singhaditya21@users.noreply.github.com> --- .Jules/palette.md | 3 +++ web-demo/js/app.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 .Jules/palette.md diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..6f311d1 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Async Loading States with Mock APIs +**Learning:** When adding UI loading states (spinners, disabled buttons) to applications using an in-memory mock client that bypasses the native `fetch` API, transitions happen instantly, making the loading state imperceptible to users and untestable. Furthermore, using `e.submitter` to manipulate the submit button requires carefully handling the original state restoration within a `finally` block to guarantee recovery regardless of API outcome. +**Action:** Always introduce an explicit awaitable delay (e.g., `await new Promise(resolve => setTimeout(resolve, delay))`) when simulating mock async behavior so UX loading states are visible. When manipulating submit buttons during forms, capture the `e.submitter` context safely, apply states (`disabled`, `aria-busy`), and strictly reset those states in a `finally` block. diff --git a/web-demo/js/app.js b/web-demo/js/app.js index 11508de..45b2dab 100644 --- a/web-demo/js/app.js +++ b/web-demo/js/app.js @@ -144,9 +144,19 @@ class ClimaAI { e.preventDefault(); const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; + const submitBtn = e.submitter; + let originalContent = ''; + + if (submitBtn) { + originalContent = submitBtn.innerHTML; + submitBtn.disabled = true; + submitBtn.setAttribute('aria-busy', 'true'); + submitBtn.innerHTML = 'Signing in...'; + } try { this.showToast('Logging in...', 'info'); + await new Promise(resolve => setTimeout(resolve, 500)); // Make mock loading perceptible const response = await api.login(email, password); this.user = response.user; this.showToast('Welcome back! 🌤️', 'success'); @@ -155,6 +165,12 @@ class ClimaAI { this.checkSubscription(); } catch (error) { this.showToast(error.message || 'Login failed', 'error'); + } finally { + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.removeAttribute('aria-busy'); + submitBtn.innerHTML = originalContent; + } } } @@ -163,9 +179,19 @@ 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 originalContent = ''; + + if (submitBtn) { + originalContent = submitBtn.innerHTML; + submitBtn.disabled = true; + submitBtn.setAttribute('aria-busy', 'true'); + submitBtn.innerHTML = 'Signing up...'; + } try { this.showToast('Creating account...', 'info'); + await new Promise(resolve => setTimeout(resolve, 500)); // Make mock loading perceptible const response = await api.register(email, password, name); this.user = response.user; this.showToast('Account created! Welcome! 🎉', 'success'); @@ -174,6 +200,12 @@ class ClimaAI { this.checkSubscription(); } catch (error) { this.showToast(error.message || 'Registration failed', 'error'); + } finally { + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.removeAttribute('aria-busy'); + submitBtn.innerHTML = originalContent; + } } }