From 047c3da1c563e826aa5d387da2aee3feaac24155 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 05:11:43 +0000 Subject: [PATCH] feat: add explicit loading states to auth buttons Added explicit visual loading states (spinner and disabled status) to all authentication forms and buttons to provide immediate user feedback during asynchronous operations. Co-authored-by: singhaditya21 <53948039+singhaditya21@users.noreply.github.com> --- .Jules/palette.md | 3 ++ web-demo/js/app.js | 86 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 .Jules/palette.md diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..79451ff --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 2024-05-14 - explicit loading states for asynchronous ui interactions +**Learning:** For asynchronous ui interactions without obvious native loading indicators (like form submissions or oauth redirects), explicit visual states are crucial. Users quickly assume the app is frozen or ignore repeated clicks if feedback isn't immediate, especially when network latency or simulated delays are involved. +**Action:** When implementing async form submissions or simulated delays (e.g. auth flows), always add a visual loading state (like '⏳ Loading...' and disabled status) to the interactive element, ensuring safe property access (`e.submitter`) and reliable state restoration in `finally` blocks by using awaitable Promises. diff --git a/web-demo/js/app.js b/web-demo/js/app.js index 11508de..38628ef 100644 --- a/web-demo/js/app.js +++ b/web-demo/js/app.js @@ -144,6 +144,14 @@ class ClimaAI { e.preventDefault(); const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; + let originalText = ''; + + if (e.submitter) { + originalText = e.submitter.innerHTML; + e.submitter.innerHTML = '⏳ Loading...'; + e.submitter.disabled = true; + e.submitter.style.opacity = '0.7'; + } try { this.showToast('Logging in...', 'info'); @@ -155,6 +163,12 @@ class ClimaAI { this.checkSubscription(); } catch (error) { this.showToast(error.message || 'Login failed', 'error'); + } finally { + if (e.submitter) { + e.submitter.innerHTML = originalText; + e.submitter.disabled = false; + e.submitter.style.opacity = '1'; + } } } @@ -163,6 +177,14 @@ class ClimaAI { const name = document.getElementById('registerName').value; const email = document.getElementById('registerEmail').value; const password = document.getElementById('registerPassword').value; + let originalText = ''; + + if (e.submitter) { + originalText = e.submitter.innerHTML; + e.submitter.innerHTML = '⏳ Loading...'; + e.submitter.disabled = true; + e.submitter.style.opacity = '0.7'; + } try { this.showToast('Creating account...', 'info'); @@ -174,6 +196,12 @@ class ClimaAI { this.checkSubscription(); } catch (error) { this.showToast(error.message || 'Registration failed', 'error'); + } finally { + if (e.submitter) { + e.submitter.innerHTML = originalText; + e.submitter.disabled = false; + e.submitter.style.opacity = '1'; + } } } @@ -186,6 +214,15 @@ class ClimaAI { } async handleGoogleSignIn() { + const btn = document.getElementById('googleSignInBtn'); + let originalText = ''; + if (btn) { + originalText = btn.innerHTML; + btn.innerHTML = '⏳ Loading...'; + btn.disabled = true; + btn.style.opacity = '0.7'; + } + try { this.showToast('🔐 Signing in with Google...', 'info'); @@ -197,31 +234,36 @@ class ClimaAI { // 5. Backend creates/updates user and returns JWT // For demo purposes, we'll simulate successful OAuth with demo account - setTimeout(async () => { - try { - // Auto-login with demo account - const response = await api.login('demo@climaai.com', 'Test1234'); - this.user = response.user; - this.showToast('✅ Welcome! Signed in with Google', 'success'); - this.showScreen('homeScreen'); - this.loadWeatherData(); - this.checkSubscription(); - } catch (error) { - this.showToast('Google Sign-In succeeded! Welcome!', 'success'); - // Create a demo user object - this.user = { - email: 'google-user@gmail.com', - full_name: 'Google User', - is_premium: true - }; - this.isPremium = true; - this.showScreen('homeScreen'); - this.loadWeatherData(); - } - }, 1500); // Simulate OAuth redirect delay + await new Promise(resolve => setTimeout(resolve, 1500)); + try { + // Auto-login with demo account + const response = await api.login('demo@climaai.com', 'Test1234'); + this.user = response.user; + this.showToast('✅ Welcome! Signed in with Google', 'success'); + this.showScreen('homeScreen'); + this.loadWeatherData(); + this.checkSubscription(); + } catch (error) { + this.showToast('Google Sign-In succeeded! Welcome!', 'success'); + // Create a demo user object + this.user = { + email: 'google-user@gmail.com', + full_name: 'Google User', + is_premium: true + }; + this.isPremium = true; + this.showScreen('homeScreen'); + this.loadWeatherData(); + } } catch (error) { this.showToast(error.message || 'Google Sign-In failed', 'error'); + } finally { + if (btn) { + btn.innerHTML = originalText; + btn.disabled = false; + btn.style.opacity = '1'; + } } }