Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

## 2026-08-09 - Add visual loading states to auth buttons
**Learning:** Adding clear loading states (e.g. `⏳ Loading...`) to async actions like authentication prevents double submissions and gives users immediate feedback, avoiding confusion when operations take time. It's crucial to wrap restoration logic in a `finally` block to ensure buttons don't stay disabled if an error occurs.
**Action:** Consistently disable submitters on form events and interactive buttons during async work, and always use try/finally to handle cleanup, even on failure.
46 changes: 42 additions & 4 deletions web-demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class ClimaAI {

setupEventListeners() {
// Auth
document.getElementById('googleSignInBtn').addEventListener('click', () => this.handleGoogleSignIn());
document.getElementById('googleSignInBtn').addEventListener('click', (e) => this.handleGoogleSignIn(e));
document.getElementById('loginForm').addEventListener('submit', (e) => this.handleLogin(e));
document.getElementById('registerForm').addEventListener('submit', (e) => this.handleRegister(e));
document.getElementById('showRegister').addEventListener('click', (e) => {
Expand Down Expand Up @@ -145,6 +145,13 @@ class ClimaAI {
const email = document.getElementById('loginEmail').value;
const password = document.getElementById('loginPassword').value;

let originalHtml = '';
if (e.submitter) {
e.submitter.disabled = true;
originalHtml = e.submitter.innerHTML;
e.submitter.innerHTML = '⏳ Loading...';
}

try {
this.showToast('Logging in...', 'info');
const response = await api.login(email, password);
Expand All @@ -155,6 +162,11 @@ class ClimaAI {
this.checkSubscription();
} catch (error) {
this.showToast(error.message || 'Login failed', 'error');
} finally {
if (e.submitter) {
e.submitter.disabled = false;
e.submitter.innerHTML = originalHtml;
}
}
}

Expand All @@ -164,6 +176,13 @@ class ClimaAI {
const email = document.getElementById('registerEmail').value;
const password = document.getElementById('registerPassword').value;

let originalHtml = '';
if (e.submitter) {
e.submitter.disabled = true;
originalHtml = e.submitter.innerHTML;
e.submitter.innerHTML = '⏳ Loading...';
}

try {
this.showToast('Creating account...', 'info');
const response = await api.register(email, password, name);
Expand All @@ -174,6 +193,11 @@ class ClimaAI {
this.checkSubscription();
} catch (error) {
this.showToast(error.message || 'Registration failed', 'error');
} finally {
if (e.submitter) {
e.submitter.disabled = false;
e.submitter.innerHTML = originalHtml;
}
}
}

Expand All @@ -185,7 +209,15 @@ class ClimaAI {
this.showToast('Logged out successfully', 'info');
}

async handleGoogleSignIn() {
async handleGoogleSignIn(e) {
let originalHtml = '';
const btn = e && e.currentTarget;
if (btn) {
btn.disabled = true;
originalHtml = btn.innerHTML;
btn.innerHTML = '⏳ Loading...';
}

try {
this.showToast('🔐 Signing in with Google...', 'info');

Expand All @@ -197,7 +229,7 @@ class ClimaAI {
// 5. Backend creates/updates user and returns JWT

// For demo purposes, we'll simulate successful OAuth with demo account
setTimeout(async () => {
await new Promise((resolve) => setTimeout(async () => {
try {
// Auto-login with demo account
const response = await api.login('demo@climaai.com', 'Test1234');
Expand All @@ -218,10 +250,16 @@ class ClimaAI {
this.showScreen('homeScreen');
this.loadWeatherData();
}
}, 1500); // Simulate OAuth redirect delay
resolve();
}, 1500)); // Simulate OAuth redirect delay

} catch (error) {
this.showToast(error.message || 'Google Sign-In failed', 'error');
} finally {
if (btn) {
btn.disabled = false;
btn.innerHTML = originalHtml;
}
}
}

Expand Down